Search code examples
c++boost-asioboost-thread

boost::asio with boost::unique_future


According to http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/overview/cpp2011/futures.html, we can use boost::asio with std::future. But I couldn't find any information about working with boost::unique_future, which has more functions, such as then(). How can I use?


Solution

  • Boost.Asio only provides first-class support for asynchronous operations to return a C++11 std::future or an actual value in stackful coroutines. Nevertheless, the requirements on asynchronous operations documents how to customize the return type for other types, such as Boost.Thread's boost::unique_future. It requires:

    • A specialization of the handler_type template. This template is used to determine the actual handler to use based on the asynchronous operation's signature.
    • A specialization of the async_result template. This template is used both to determine the return type and to extract the return value from the handler.

    Below is a minimal complete example demonstrating deadline_timer::async_wait() returning boost:unique_future with a basic calculation being performed over a series of continuations composed with .then(). To keep the example simple, I have opted to only specialize handler_type for the asynchronous operation signatures used in the example. For a complete reference, I highly suggest reviewing use_future.hpp and impl/use_future.hpp.

    #include <exception> // current_exception, make_exception_ptr
    #include <memory> // make_shared, shared_ptr
    #include <thread> // thread
    #include <utility> // move
    
    #define BOOST_RESULT_OF_USE_DECLTYPE
    #define BOOST_THREAD_PROVIDES_FUTURE_CONTINUATION
    
    #include <boost/asio.hpp>
    #include <boost/date_time/posix_time/posix_time.hpp>
    #include <boost/thread/future.hpp>
    
    /// @brief Class used to indicate an asynchronous operation should return
    ///        a boost::unique_future.
    class use_unique_future_t {};
    
    /// @brief A special value, similiar to std::nothrow.
    constexpr use_unique_future_t use_unique_future;
    
    namespace detail {
    
    /// @brief Completion handler to adapt a boost::promise as a completion
    ///        handler.
    template <typename T>
    class unique_promise_handler;
    
    /// @brief Completion handler to adapt a void boost::promise as a completion
    ///        handler.
    template <>
    class unique_promise_handler<void>
    {
    public:
      /// @brief Construct from use_unique_future special value.
      explicit unique_promise_handler(use_unique_future_t)
        : promise_(std::make_shared<boost::promise<void> >())
      {}
    
      void operator()(const boost::system::error_code& error)
      {
        // On error, convert the error code into an exception and set it on
        // the promise.
        if (error)
          promise_->set_exception(
              std::make_exception_ptr(boost::system::system_error(error)));
        // Otherwise, set the value.
        else
          promise_->set_value();
      }
    
    //private:
      std::shared_ptr<boost::promise<void> > promise_;
    };
    
    // Ensure any exceptions thrown from the handler are propagated back to the
    // caller via the future.
    template <typename Function, typename T>
    void asio_handler_invoke(
        Function function,
        unique_promise_handler<T>* handler)
    {
      // Guarantee the promise lives for the duration of the function call.
      std::shared_ptr<boost::promise<T> > promise(handler->promise_);
      try
      {
        function();
      }
      catch (...)
      {
        promise->set_exception(std::current_exception());
      }
    }
    
    } // namespace detail
    
    namespace boost {
    namespace asio {
    
    /// @brief Handler type specialization for use_unique_future.
    template <typename ReturnType>
    struct handler_type<
        use_unique_future_t,
        ReturnType(boost::system::error_code)>
    {
      typedef ::detail::unique_promise_handler<void> type;
    };
    
    /// @brief Handler traits specialization for unique_promise_handler.
    template <typename T>
    class async_result< ::detail::unique_promise_handler<T> >
    {
    public:
      // The initiating function will return a boost::unique_future.
      typedef boost::unique_future<T> type;
    
      // Constructor creates a new promise for the async operation, and obtains the
      // corresponding future.
      explicit async_result(::detail::unique_promise_handler<T>& handler)
      {
        value_ = handler.promise_->get_future();
      }
    
      // Obtain the future to be returned from the initiating function.
      type get() { return std::move(value_); }
    
    private:
      type value_;
    };
    
    } // namespace asio
    } // namespace boost
    
    int main()
    {
      boost::asio::io_service io_service;
      boost::asio::io_service::work work(io_service);
    
      // Run io_service in its own thread to demonstrate future usage.
      std::thread thread([&io_service](){ io_service.run(); });
    
      // Arm 3 second timer.
      boost::asio::deadline_timer timer(
          io_service, boost::posix_time::seconds(3));
    
      // Asynchronously wait on the timer, then perform basic calculations
      // within the future's continuations.
      boost::unique_future<int> result =
          timer.async_wait(use_unique_future)
            .then([](boost::unique_future<void> future){
               std::cout << "calculation 1" << std::endl;
               return 21;
            })
            .then([](boost::unique_future<int> future){
              std::cout << "calculation 2" << std::endl;
              return 2 * future.get();
            })
          ;
    
      std::cout << "Waiting for result" << std::endl;
      // Wait for the timer to trigger and for its continuations to calculate
      // the result.
      std::cout << result.get() << std::endl;
    
      // Cleanup.
      io_service.stop();
      thread.join();
    }
    

    Output:

    Waiting for result
    calculation 1
    calculation 2
    42