Skip to content
This repository has been archived by the owner on Jun 12, 2018. It is now read-only.

how to get synchronous send ? how to wait() on async call ? #72

Open
timotheecour opened this issue Jun 8, 2017 · 7 comments
Open

how to get synchronous send ? how to wait() on async call ? #72

timotheecour opened this issue Jun 8, 2017 · 7 comments

Comments

@timotheecour
Copy link

timotheecour commented Jun 8, 2017

@eidheim

How can I call wait after a send (and avoid the "callback hell" pattern (https://colintoh.com/blog/staying-sane-with-asynchronous-programming-promises-and-generators) that you suggested in https://github.com/eidheim/Simple-WebSocket-Server/blob/master/ws_examples.cpp#L79 in response to #24 ?

EDIT: this seems very relevant:
https://stackoverflow.com/questions/20709725/how-to-wait-for-an-asio-handler

void bar(int value){
    typedef boost::promise<void> promise_type;
    promise_type promise;

    // Pass the handler to async operation that will set the promise.
    async_set_bar(value, boost::bind(&promise_type::set_value, &promise));

    // Synchronously wait for the future to finish.
    promise.get_future().wait();
  }
@eidheim
Copy link
Owner

eidheim commented Jun 8, 2017

Thank you for starting this discussion, it is very interesting.

First off, get_future().wait() is blocking and should not be run inside an io_service task.

Secondly, the PPL Tasks are interesting, and if I remember correctly they are proposed for the C++ standard, but will not be accepted before earliest C++20. The main problem here is that using PPL Tasks will greatly affect how you write your program. You can't for instance call await inside a regular function (that is for instance returning void) (edit: this is not entirely correct, there are ways to synchronise async calls through blocking). This added complexity somewhat counteracts the advantages of working with an event-loop.

With respect to waiting (in a nonblocking manner) for async functions, the boost way is to use boost::asio::spawn. See for instance: http://www.boost.org/doc/libs/1_64_0/doc/html/boost_asio/example/cpp11/spawn/echo_server.cpp. The problem here though is that you run functions, that could potentially run in parallell, sequentially. This is also an issue with PPL's await pattern. Additionally, the stack handing is more complex.

Finally, we have our current solution, that has none of the above drawbacks, but with the drawback of potentially leading to many nested callbacks. There is no perfect solution in my opinion, but will be following the c++ standard committee's work on asio and related additions.

@eidheim
Copy link
Owner

eidheim commented Jun 8, 2017

JavaScript's Promise is actually my favourite way of chaining async calls, but to my knowledge this is currently not possible to do with c++'s (boost::)asio. One problem I guess would be to keep the promise/future objects alive when leaving scope.

@timotheecour
Copy link
Author

timotheecour commented Jun 9, 2017

@eidheim

How about this?

boost::unique_future<int> result =
      timer.async_wait(use_unique_future)
        .then([](boost::unique_future<void> future){
           std::cout << "calculation 1" << std::endl;
           return 21;
        })
        .then([](boost::unique_future<int> future){
          std::cout << "calculation 2" << std::endl;
          return 2 * future.get();
        })
      ;

http://www.boost.org/doc/libs/1_55_0/doc/html/thread/synchronization.html#thread.synchronization.futures.reference.unique_future.then

#include <boost/thread/future.hpp>
using namespace boost;
int main()
{
  future<int> f1 = async([]() { return 123; });
  future<string> f2 = f1.then([](future<int> f) { return f.get().to_string(); // here .get() won't block });
}

@eidheim
Copy link
Owner

eidheim commented Jun 9, 2017

In http://www.boost.org/doc/libs/1_64_0/doc/html/thread/synchronization.html#thread.synchronization.futures.reference.unique_future.then you have the line (under Notes):
"The returned futures behave as the ones returned from boost::async, the destructor of the future object returned from then will block. This could be subject to change in future versions.". One would have to currently keep the future object alive then, or else it will block.

I'll look into this further in the following days.

@timotheecour
Copy link
Author

relavant to what you just said:
https://stackoverflow.com/a/12527892/1426932 create the promise/future pair right in run(), and pass the promise to the thread

void callback(std::promise<void> p)
    {
        _job();
        p.set_value();
    }

@eidheim
Copy link
Owner

eidheim commented Jun 9, 2017

In your above link, synchronous_job::run is blocking. Our case is a bit different than the one described in the link, since both the promise/future and the callback should run in the same thread, for instance in the event loop (which is the default threading strategy in Simple-Web(Socket)-Server). Additionally, the promise/future should be kept alive without returning the future (as in JavaScript where the promises are kept alive through garbage collection I guess). I think PPL Tasks solves this by always returning the tasks until eventually a wait() (or something similar) is called on the task chain.

@eidheim
Copy link
Owner

eidheim commented Jun 9, 2017

I was a bit unclear above, what I mean by running in the same thread, is that future::wait() should never be called, and future::get should only be called within the future::then callback, that is after promise::set is called. The future::then callback should also be run within a posted io_service task, and promise::set should be called within an io_service task as well (but a different posted task).

edit: changed future::then to: the future::then callback

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants