Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

defer (5 of 7 overloads)
PrevUpHomeNext

Submits a function to be run on a specified target executor, and passes the result to a completion handler.

template<
    typename Function,
    typename Executor,
    typename CompletionToken = default_completion_token_t<Executor>>
auto defer(
    Function && function,
    const Executor & ex,
    CompletionToken && token = default_completion_token_t< Executor >(),
    constraint_t< !is_void< result_of_t< decay_t< Function >()> >::value >  = 0,
    constraint_t<(execution::is_executor< Executor >::value &&can_require< Executor, execution::blocking_t::never_t >::value)||is_executor< Executor >::value >  = 0);

This function submits a function object for execution on the specified executor. The function object is queued for execution, and is never called from the current thread prior to returning from defer(). After the submitted function completes, the completion handler is dispatched along with the function's result, to run on its associated executor.

The use of defer(), rather than post, indicates the caller's preference that the executor defer the queueing of the function object. This may allow the executor to optimise queueing for cases when the function object represents a continuation of the current call context.

Parameters

function

A nullary function to be executed on the target executor.

ex

The target executor.

token

The completion token that will be used to produce a completion handler. The function signature of the completion handler must be:

void handler(decay_t<result_of_t<decay_t<Function>()>>);
Return Value

This function returns async_initiate<CompletionToken, void()>(Init{ex}, token), where Init is a function object type defined as:

class Init
{
public:
  using executor_type = Executor;
  explicit Init(const Executor& ex) : ex_(ex) {}
  executor_type get_executor() const noexcept { return ex_; }
  template <typename CompletionHandler>
    void operator()(CompletionHandler&& completion_handler,
      Function&& function) const;
private:
  Executor ex_; // exposition only
};

The function call operator of Init:

  • Obtains the handler's associated executor object ex1 of type Ex1 by performing

    auto ex1 = get_associated_executor(completion_handler, ex);

  • Obtains the handler's associated allocator object alloc by performing

    auto alloc = get_associated_allocator(completion_handler);

  • If execution::is_executor<Ex1>::value is true, constructs a function object wrapper f with a member executor_ that is initialised with prefer(ex1, execution::outstanding_work.tracked), a member function_ that is a decay-copy of function, a member handler_ that is a decay-copy of completion_handler, and a function call operator that performs:

    auto result = std::move(function_)(); auto a = get_associated_allocator(handler_); prefer(executor_, execution::allocator(a)).execute( std::bind(std::move(handler_), std::move(result)));

  • If execution::is_executor<Ex1>::value is false, constructs a function object wrapper f with a member work_ that is initialised with make_work_guard(ex1), a member function_ that is a decay-copy of function, a member handler_ that is a decay-copy of completion_handler, and a function call operator that performs:

    auto result = std::move(function_)(); auto a = get_associated_allocator(handler_); work_.get_executor().dispatch( std::bind(std::move(handler_), std::move(result)), a); work_.reset();

  • If execution::is_executor<Executor>::value is true, performs

    prefer( require(ex, execution::blocking.never), execution::relationship.fork, execution::allocator(alloc) ).execute(std::move(f));

  • If execution::is_executor<Executor>::value is false, performs

    ex.defer(std::move(f), alloc);

Remarks

If the function object throws an exception, that exception is allowed to propagate to the target executor. The behaviour in this case is dependent on the executor. For example, io_context will allow the exception to propagate to the caller that runs the io_context, whereas thread_pool will call std::terminate.

Example

This defer overload may be used to submit long running work to a thread pool and, once complete, continue execution on an associated completion executor, such as a coroutine's associated executor:

boost::asio::awaitable<void> my_coroutine()
{
   // ...

   int result = co_await boost::asio::defer(
       []{
         return perform_expensive_computation();
       },
       my_thread_pool);

   // handle result on the coroutine's associated executor
}
Completion Signature
void(decay_t<result_of_t<decay_t<Function>()>>)

PrevUpHomeNext