5

I was writing some asio code and tried to refactor it to use C++20 coroutines. However I got stuck transforming this code:

asio::post(
    my_strand,
    [self = shared_from_this()]() {
        // functions that write in this container can only be called
        // on a single thread at a time, thus the strand
        session_write_history.push_back(buffer);
        /* co_await? */ socket.write_async(buffer, /* use awaitable? */);
    }
);

You see, my async operation is done inside the post callback, so using asio::use_awaitable on the async operation one would make the callback a coroutine. Is there a way to await on the async operation inside the asio::post on the strand?

stark
  • 12,615
  • 3
  • 33
  • 50
Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141

1 Answers1

6

Simply send asio::use_awaitable instead of a callback to post. This will make your function awaitable. You will then be able to put your async calls directly in your function:

co_await asio::post(my_strand, asio::use_awaitable);
// ...
// code that runs on the strands context
// ...
co_await socket.async_write_some(buffer, asio::use_awaitable);

You can in fact, use asio::use_awaitable in any place where you would put a callback, so transforming callback async code into coroutines can be done pretty much 1:1

Guillaume Racicot
  • 39,621
  • 9
  • 77
  • 141
  • Good analysis. It would be better if you clarified that the first sample line is a no-op and only there to show the consistency of the library interface? – sehe Feb 16 '21 at 22:02
  • @sehe it is not a no-op. When running on multiple thread, this line ensure the rest of the function runs on the strand's context. – Guillaume Racicot Feb 16 '21 at 22:16
  • Wouldn't it run on the socket's associated executor after the async_write_some anyways? (You're right about the immediate call to the initiating function for async_read_some though). It feels like there should be a better possibility (like associating the strand executor with the socket in the first place, binding the completion handler to the strand etc.). I'm not sure though - I didn't play with the awaitables much yet (Oh and the `coawait this:coro::executor` idiom seems relevant) – sehe Feb 16 '21 at 22:20
  • 1
    @sehe in my actual code I have other actions that I must execute in a particular strand, independent of the execution context of the socket. I can add a note in my snippets. – Guillaume Racicot Feb 16 '21 at 22:33
  • @GuillaumeRacicot thank you for this. How do you return then to the original (outer) execution context? – sleep Nov 04 '21 at 23:26
  • You might not be able to if the previous execution context wasn't in an executor to begin with. But if it was, you might just do `co_await asio::post(previous_executor, asio::use_awaitable)` – Guillaume Racicot Nov 05 '21 at 00:48
  • @GuillaumeRacicot I've tried this approach and another. I can't get the result we want: https://gist.github.com/sketch34/9a3aa53887892b2181bc389003741118 – sleep Feb 10 '22 at 04:50
  • @JarrodSmith You will have to ask another question for that. My problem was solved. – Guillaume Racicot Feb 10 '22 at 15:07
  • @JarrodSmith I looked at your gist and I think my question may be about the same issue: https://stackoverflow.com/q/71987021/4479969 – raldone01 Apr 24 '22 at 18:02