I have a function that returns boost::asio::awaitable
. What is the idiomatic way to convert this awaitable to std::future
?

- 2,035
- 2
- 19
- 35
-
1@Frank in its brilliant simplicity that answer has the potential to enlighten a great number of people. Would you care to post that as answer? – sehe Jun 11 '21 at 08:29
-
1@sehe I don't know if "the library has a button for that" makes for much of a "brilliant" answer, but upon reflection, you are right that this has the potential to unblock a lot of people. Thanks for the prompt. I've removed the previous comment because I feel like the caveat in the answer's foreword is important should this become a commonly-used Q/A. – Jun 11 '21 at 13:02
1 Answers
Before we get into the answer, be warned:
You should not, under any circumstance, get()
or wait()
a future to a boost::asio::awaitable
from the same thread as the executor that is running the coroutine.
That being said.
That third parameter to co_spawn()
, the one almost every example blindly sets to the magic detached
constant? Its role is to tell boost::asio
what to do once the coroutine has finished. detached
simply means "do nothing". So the canonical way to fulfil a future from an awaitable<>
should be via that mechanism.
Thankfully, asio already provides the use_future
completion token. Pass that as the third parameter to co_spawn()
and it will return a std::future<>
of the matching return type.
boost::asio::awaitable<void> foo();
boost::asio::awaitable<int> bar();
std::future<void> foo_fut = boost::asio:co_spawn(io_context, foo(), boost::asio::use_future);
std::future<int> bar_fut = boost::asio:co_spawn(io_context, bar(), boost::asio::use_future);
Here's a full example:
#include <boost/asio/io_context.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/use_future.hpp>
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
using boost::asio::awaitable;
using boost::asio::co_spawn;
using boost::asio::io_context;
using boost::asio::use_future;
awaitable<void> foo() {
// Simulate foo taking a while to run
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "foo\n";
co_return;
}
int main() {
try {
io_context context;
std::future<void> fut = co_spawn(context, foo(), use_future);
std::thread waiter([fut=std::move(fut)](){
std::cout << "AAA\n";
fut.wait();
std::cout << "BBB\n";
});
context.run();
waiter.join();
} catch(const std::exception &ex) {
std::cerr << ex.what() << std::endl;
}
return 0;
}
Obviously, this means you need access to the io context in order to produce the future.
If you are already inside of a coroutine and have no access to the io_context, then you have to create the future before calling the subroutine, and co_await
its result before fullfilling the future. std::promise<>
is perfectly suited for that task:
awaitable<void> sub_foo() {
co_return;
}
awaitable<void> foo() {
std::promise<void> prom;
auto fut = prom.get_future();
// send the future somewhere
co_await sub_foo();
prom.set_value();
co_return;
}
If you are not within a coroutine, and do not have access to an io_context, then you can't invoke the coroutine in the first place. So either of these approaches should have you covered.