[asio] writing composed operations example
Hi everyone, as a long time asio user I often had needs to wrap up repeating patterns of async calls for re-use. Normally I went for some kind of specialized object that wraps smaller pieces but I recently came across a series of examples which try to explain how own composed operations may be created. https://www.boost.org/doc/libs/1_71_0/doc/html/boost_asio/examples/cpp11_exa... Now, looking at the examples they are of course way over my head but since own composed operations would be very much exactly what I need I thought I'd give it a shot. But as I now look at this example here: https://www.boost.org/doc/libs/1_71_0/doc/html/boost_asio/example/cpp11/oper... I notice something that I hope somebody can explain. In the coroutine implementation it says: delay_timer_->expires_after(std::chrono::seconds(1)); yield delay_timer_->async_wait(std::move(self)); if (error) break; yield boost::asio::async_write(socket_, boost::asio::buffer(*encoded_message_), std::move(self)); I'm puzzled about the double std::move() of self. In the comments above it says that self is a reference to an intermediate completion handler. How can this be moved twice? Can anyone give me hint about what this means? I would also appreciate any hints in general about own composed operations. Perhaps there are other approaches that are a bit less intimidating? Cheers, Stephan
Hello Stephan, I have use stackless coroutines a little bit, and I think, I can explain.
I'm puzzled about the double std::move() of self. In the comments above it says that self is a reference to an intermediate completion handler. How can this be moved twice? Can anyone give me hint about what this means?
The line yield delay_timer_->async_wait(std::move(self)); terminates the function. If the async operation is complete, the function gets called again and continues at the next statement, but this time with the new, moved self. So the next line yield boost::asio::async_write(…, …, std::move(self)); can move self a 2nd time. When the 2nd async operation completes and execution continues on the next line, the statement self.complete(error); finally operates on the 3rd instance of self. I hope this helps, Mario
On Mon, 2 Sep 2019 at 10:30, Stephan Menzel via Boost-users
I'm puzzled about the double std::move() of self. In the comments above it says that self is a reference to an intermediate completion handler. How can this be moved twice? Can anyone give me hint about what this means?
There is anything in particular from composed_8.cpp that you find confusing? The multiple moving thing is no different than in composed_6.cpp or composed_7.cpp. For the explanation I will mention line numbers, it may be easier to look at https://github.com/boostorg/asio/blob/boost-1.71.0/example/cpp11/operations/... * In line 150 an async_write_messages_implementation object is created: objectA * In line 89 objectA is moved into objectB. async_wait stores objectB. This should not surprise you, even if boost::asio::async_compose hides the fact a bit, async_write_messages is doing the same. Where is the lambda created in line 167 being stored? It's being stored by async_write_messages. async_compose is creating the class able to store such a lambda, and you are passing the lambda to it in line 153 (as "token"). * After line 89 line 90 is NOT executed, the "yield" makes it leave the "reenter" body. And since there is nothing else after that operator() returns. objectA gets out of scope and destroyed here. * After one second the wait finishes. And it executes objectB.operator(). Once it reaches the "reenter" the magic of boost::asio::coroutine (it's just a switch) makes it skip to line 90. * In line 94 objectB is moved into objectC and by the same logic than before objectB gets destroyed. * Once async_write does its job objectC.operator() will be called again, the reenter will continue from line 95 and finish the job. So no object is moved multiple times, it's a different object each time. Notice that's why in line 54 and 60 those objects are being created with unique_ptr. If encoded_message_ were part of async_write_messages_implementation it would be moved away, and the async_write from line 93 could be writing the contents of a moved-from std::string (it's undefined behaviour what exactly it would do).
I would also appreciate any hints in general about own composed operations. Perhaps there are other approaches that are a bit less intimidating?
Once you get used to it it's not really that intimidating. All the composed operations follow the same pattern and other than the "operator()" it's a lot of repetitive boilerplate, quite boring actually. I know Boost.Beast is described as a "Portable HTTP, WebSocket, and network operations using only C++11 and Boost.Asio" library and you have no reason to look into it unless you need to HTTP/WebSocket. But if you do you will find nowadays it's much more than that, it's also a generic library on top of ASIO, slightly higher lever with very useful helpers... and more user-friendly documentation. If you look at https://www.boost.org/doc/libs/1_71_0/libs/beast/doc/html/index.html you will find a whole "Writing Composed Operations" section in the documentation. And look at https://www.boost.org/doc/libs/1_71_0/libs/beast/doc/html/beast/ref/boost__b... What I said about "encoded_message_" having to be a pointer. The example from the ASIO docs is actually slightly wrong (i.e. simplified), that std::string should ideally be allocated with the handler-associated allocator. "Allocators? I did once hear somebody say the word, I don't know anything else about them" are you saying? Don't worry, you just need to use Beast allocate_stable() and the correct allocator will be used.
Hello Christian and Mario, thanks for your responses. I think I'm much closer to an implementation now. Am Mo., 2. Sept. 2019 um 13:57 Uhr schrieb Cristian Morales Vega < cristian@samknows.com>:
I would also appreciate any hints in general about own composed operations. Perhaps there are other approaches that are a bit less intimidating?
Once you get used to it it's not really that intimidating. All the composed operations follow the same pattern and other than the "operator()" it's a lot of repetitive boilerplate, quite boring actually.
Yes, as I was moving forward this turnes out to be the problem now. Essentially, most of the operations I have in mind combine tasks with different completion token types. For example async_resolve() and async_connect(). Once I saw how operator() basically acts as a handler for all the async ops within this question became the most interesting one.
If you look at https://www.boost.org/doc/libs/1_71_0/libs/beast/doc/html/index.html you will find a whole "Writing Composed Operations" section in the documentation.
I did have a look at Vince's tutorial but tbh I wanted so see the problems that would be simplified first before jump right ahead. I guess that's where I'm now ;-) Cheers, Stephan
participants (3)
-
Cristian Morales Vega
-
Klebsch, Mario
-
Stephan Menzel