Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to convert boost::asio::awaitable to std::future?

Tags:

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

like image 677
Sogartar Avatar asked Jun 09 '21 16:06

Sogartar


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.

like image 171
Frank Avatar answered Oct 14 '22 05:10

Frank