Reputation: 173
I am spawning a coroutine as shown below.
asio::co_spawn(executor, my_coro(), asio::detached);
How am I supposed to cancel it?
As far as I know, per handler cancellation can be achieved simply by binding a handler with asio::bind_cancellation_slot
. This does not work in my specific example (irrespective of using the asio::detached "handler" or some lambda), and it makes sense to me that it does not work.
I found this blog post which basically says that spawning the coroutine from another one and awaiting it (with appropriate asio::bind_cancellation_slot
setup) does the trick. This fails for me as my_coro
never seems to run in the example below. Besides, I don't even know why this should work at all (with respect to cancellation).
asio::awaitable<void> cancelable(asio::cancellation_signal& sig, asio::awaitable<void>&& awaitable) {
co_await asio::co_spawn(co_await asio::this_coro::executor, std::move(awaitable), asio::bind_cancellation_slot(sig.slot(), asio::use_awaitable));
co_return;
}
// ...
asio::cancellation_signal signal;
asio::co_spawn(executor, cancelable(signal, my_coro()), asio::detached);
What is the correct way to connect a asio::cancellation_signal
to the asio::cancellation_state
of a coroutine (obtained by co_await asio::this_coro::cancellation_state
) in ASIO?
If it helps: I'm using ASIO stand-alone on the most recent master, i.e. not the boost version.
Upvotes: 5
Views: 3149
Reputation: 782
To cancel a coroutine in asio use the new awaitable_operator '||'. The awaitable operator '||' allows to co_await for more than one coroutine until one of the coroutines is finished. For example:
#include "boost/asio/experimental/awaitable_operators.hpp"
#include <boost/asio.hpp>
#include <boost/asio/deadline_timer.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/system_timer.hpp>
#include <boost/asio/this_coro.hpp>
#include <chrono>
#include <iostream>
boost::asio::awaitable<void>
printHelloEverySecond ()
{
for (;;)
{
std::cout << "Hello" << std::endl;
boost::asio::system_timer timer{ co_await boost::asio::this_coro::executor };
timer.expires_after (std::chrono::seconds{ 1 });
co_await timer.async_wait (boost::asio::use_awaitable);
}
}
boost::asio::awaitable<void>
stopAfter4500Milliseconds (boost::asio::system_timer &timer)
{
boost::asio::system_timer stopAfterFiveSeconds{ co_await boost::asio::this_coro::executor };
stopAfterFiveSeconds.expires_after (std::chrono::milliseconds{ 4500 });
co_await stopAfterFiveSeconds.async_wait (boost::asio::use_awaitable);
timer.cancel ();
}
boost::asio::awaitable<void>
cancel (boost::asio::system_timer &timer)
{
try
{
co_await timer.async_wait (boost::asio::use_awaitable);
}
catch (boost::system::system_error &e)
{
using namespace boost::system::errc;
if (operation_canceled == e.code ())
{
// swallow cancel
}
else
{
std::cout << "error in timer boost::system::errc: " << e.code () << std::endl;
abort ();
}
}
}
int
main ()
{
boost::asio::io_context ioContext{};
boost::asio::system_timer timer{ ioContext };
timer.expires_at (std::chrono::system_clock::time_point::max ());
using namespace boost::asio::experimental::awaitable_operators;
co_spawn (ioContext, printHelloEverySecond () || cancel (timer), [] (auto, auto) { std::cout << "stopped" << std::endl; });
co_spawn (ioContext, stopAfter4500Milliseconds (timer), boost::asio::detached);
ioContext.run ();
}
Here "printHelloEverySecond ()" will run until "cancel()" returns successfully which happens when the timer is canceled (in "stopAfter4500Milliseconds") after 4500 milliseconds. This allows to cancel "printHelloEverySecond ()" when the timer gets canceled.
For reference:
Chris Kohlhoff explains awaitable_operator and cancelation
Upvotes: 6