Search code examples
c++boostboost-asiofuture

How can I get a future from boost::asio::post?


I am using Boost 1.66.0, in which asio has built-in support for interoperating with futures (and for some time now). The examples I've seen online indicate how to achieve this cleanly when using networking functions such as async_read, async_read_some, etc. That is done by providing boost::asio::use_future in place of the completion handler, which causes the initiating function to return a future as expected.

What kind of object do I need to provide or wrap my function in to get the same behavior from boost::asio::post?

My purpose for posting the work is to execute it in the context of a strand but otherwise wait for the work to complete, so I can get the behavior I want doing:

std::packaged_task<void()>  task( [] { std::cout << "Hello world\n"; } );
auto  f = task.get_future();
boost::asio::post(
    boost::asio::bind_executor(
        strand_, std::move( task ) ) );
f.wait();

but according to the boost::asio documentation, the return type for boost::asio::post is deduced in the same way as for functions like boost::asio::async_read, so I feel like there has to be a nicer way that can avoid the intermediate packaged_task. Unlike async_read there is no "other work" to be done by post so providing just boost::asio::use_future doesn't makes sense, but we could define an async_result trait to get the same behavior for post.

Is there a wrapper or something that has the necessary traits defined to get the behavior I want or do I need to define it myself?


Solution

  • @MartiNitro's idea with packaged_task has become part of the library: now you can just post a packaged_task and it will magically return its future:

        auto f = post(strand_, std::packaged_task<int()>(task));
    

    Update

    Thanks to @niXman's comment, discovered a more convenient interface on the use_future token:

        auto f = post(ex, boost::asio::use_future(task));
    

    Live Demo

    #include <boost/asio.hpp>
    #include <iostream>
    #include <future>
    using namespace std::chrono_literals;
    
    int task() {
        std::this_thread::sleep_for(1s);
        std::cout << "Hello world\n";
        return 42;
    }
    
    int main() {
        boost::asio::thread_pool ioc(1);
        auto                     ex = ioc.get_executor();
    
        auto f = post(ex, std::packaged_task<int()>(task));
        // optionally wait for future:
        f.wait();
        // otherwise .get() would block:
        std::cout << "Answer: " << f.get() << "\n";
    
        f = post(ex, boost::asio::use_future(task));
        f.wait();
        std::cout << "Second answer: " << f.get() << "\n";
    
        ioc.join();
    }
    

    Prints

    Hello world
    Answer: 42
    Hello world
    Second answer: 42