Assume a thread is running the asio event loop, how can I defer a lambda to be called in this thread?
#include <thread>
#include <iostream>
#include <asio.hpp>
int main()
{
asio::io_context io_context;
// hook something into io_context
std::jthread t([&]{io_context.run();});
// now how to let eventoop thread call lambda
io_context.async_call([]{std::cout<<"Hello World!";}); // this method does not exist
}
I am looking for a functionality in the spirit of http://docs.libuv.org/en/v1.x/async.html#c.uv_async_t, in libuv this is possible with uv_async_send these guarantees:
-
Async handles allow the user to “wakeup” the event loop and get a
callback called from another thread. -
It’s safe to call this function from any thread. The callback will be
called on the loop thread. -
uv_async_send() is async-signal-safe. It’s safe to call this function
from a signal handler.
What would be the equivalent in asio?
>Solution :
In Asio you post[1] work to an executor.
The executor is associated with an execution context. In your example io_context is such an execution context.
So, you would say:
asio::post(io_context.get_executor(), []{std::cout<<"Hello World!";});
ADL makes it so that you can probably use unqualified:
post(io_context.get_executor(), []{std::cout<<"Hello World!";});
Next up, as a convenience Asio supports posting “to” an execution context, in which case its default executor will be used:
post(io_context, []{std::cout<<"Hello World!";});
Differences
If you use asio::dispatch the posted handler may be invoked on the calling thread iff the executor matches that thread. E.g. in the given example asio::dispatch would behave just like asio::post unless you used it from inside a handler already running on the “service thread” (a thread running the io context).
Associated Executors
There are multiple types of executors. They might add custom behavior. For example strand<> executors make sure only a single handler runs concurrently, enabling you to get serialization of access similarly to mutual exclusion in synchronous code.
It stands to reason the choice of executor will be important for this mechanism to be correct. The library has an “associated executor” facility to allow it to honor executor requirements correctly in generic code.
For example, composed operations (like asio::async_read_until) will correctly use the associated executor even for any intermediate handlers that it posts internally.
To associate an executor, use bind_executor, e.g.:
#include <iostream>
#include <boost/asio.hpp>
int main() {
boost::asio::thread_pool tp; // multi threading execution context
auto strand = make_strand(tp.get_executor()); // using a strand to serialize handlers
auto handler = [&strand] { std::cout << "On the strand: " << strand.running_in_this_thread() << std::endl; };
auto bound = bind_executor(strand, handler);
// Now even posting _without_ an executor will invoke the bound handler on the intended executor:
post(bound);
tp.join();
}
Which prints
On the strand: 1
See for more related information:
- boost::asio::bind_executor does not execute in strand
- Correct way to put all asynchronous operations of an object into a boost::asio::strand
- Which io_context does std::boost::asio::post / dispatch use?
- What is the difference between post and dispatch from boost::asio when overloads with executor parameter are used?
More related to the core execution guarantees of the library:
[1] asio::post, asio::defer, asio::dispatch