Given the following test program:
#include <asio.hpp>
#include <cassert>
int main()
{
asio::io_service ios1, ios2;
asio::io_service::strand s2(ios2);
auto test_func = wrap(s2, [&] {
assert(s2.running_in_this_thread());
});
auto wrap_test_func = wrap(ios1, test_func);
wrap_test_func();
ios1.run_one();
ios2.run_one();
}
My understanding is that this program should not assert.
wrap_test_func
is wrapped into io_service
ios1
. The function it is wrapping is wrapped into strand
s2
(which uses ios2
).
As I understand it, calling wrap_test_func
should be equivalent to dispatch(ios1, test_func)
which should then dispatch the lambda in s2
).
However, it looks as if wrap has unwrapped the inner wrapper.
Is this expected behaviour?
Turns out that it was my misunderstanding.
Here's a copy of the reply by Asio's author:
Hi Richard,
Yes this behaviour is deliberate. However, in the networking TS and the latest code on the master branch this facility has been renamed from wrap (which implied adding another layer as you expected) to bind_executor. This function simply imbues the object with an associated executor; if it already had one it is overridden.
If you need true wrapping then you should explicitly wrap your inner handler in an outer function object (or lambda), and have the outer handler dispatch() the inner handler on its "associated executor". As you're writing your own asynchronous operation, I suggest adopting the following pattern (documented in the networking TS as part of the "requirements on asynchronous operations"):
Ask the handler for its associated executor using get_associated_executor.
post() the handler to that executor if your operation finishes immediately.
dispatch() the handler to that executor otherwise.
So (untested code, may require tip of master branch):
template<class Task, class Handler> void async_execute(implementation& impl, Task&& task, Handler&& handler) { ... auto ex = asio::get_associated_executor(handler get_io_context()); // this is immediate completion, so we use post() if (not impl) { post(ex, [handler = std::forward<Handler>(handler)]() mutable { promise_type promise; promise.set_exception(std::make_exception_ptr(system_error(errors::null_handle))); handler(promise.get_future()); }); return; } // this is not immediate completion, so we use dispatch() // (NOTE: assumes this->post_execute() does not run the task) // Optional. Really only needed if your io_context participates in the // async operation in some way not shown in this snippet, and not // simply as a channel for delivering the handler. auto io_work = make_work_guard(get_io_contet()); auto handler_work = make_work_guard(ex); auto impl_ptr = impl.get(); auto async_handler = [this, ex, impl_ptr, io_work, handler_work, handler = std::forward<Handler>(handler)] (detail::long_running_task_op::identifier ident, auto future) mutable { assert(impl_ptr); io_work.reset(); dispatch(ex, [handler = std::move(handler), future = std::move(future)]() mutable { handler(std::move(future)); }); assert(impl_ptr); impl_ptr->remove_op(ident); }; ... this->post_execute(); }
Hope this helps.
Cheers, Chris
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With