detail::async_result

Module: Asynchronous Operations

An object that represents the eventual completion or failure of an asynchronous operation and its resulting value.

Template Parameters:

  • T The type of the value produced by the asynchronous operation. If non-void, the value is passed as an rvalue reference to the result callback. Otherwise, the result callback does not take any arguments.

  • traits The type of traits for configuring internal types used by the async_result. The traits should be provided as a structure which contains definitions of the following types:

  • The lock_type and the lock_guard_type. The lock_type needs to be constructible, while the lock_guard_type must have the std::lock_guard<lock_type> semantics. By using lock_type = std::mutex and lock_guard_type = std::lock_guard<std::mutex> the async_result is made thread-safe and the resolution can occur on a different thread than the one which sets the callbacks.

  • The callback_type that should be a template functor class constructible from a lambda. This type must support the bool() operator and move-assignment, but does not need to be copy-constructible and does not need a const operator. A simple implementation of the traits type would just define the callback_type using callback_type = std::function;

The asynchronous operation may be in one of these states:

  • Pending: when the asynchronous operation is pending
  • Resolved: when the asynchronous operation is finished
    The asynchronous operation may produce a resource, which can be passed to the application, or may not produce anything and only serve and mutate a state. The first template parameter of the async_result object denotes the type of the resource produced by the async operation, with the void type used if the operation does not produce anything besides information about its completion. This type is called a value type of the async operation and the value type of the async_result.

The async_result object may be in one of these states:

  • Pending: The asynchronous operation is pending.
  • Resolved: The asynchronous operation is resolved.
  • Finalized: Introduced by a user of the async_result object by doing operations which consume the async_result object, such as moving the object (move-construction or move-assignment) or setting any callbacks. This state is independent of the asynchronous operation state and means that the async_result object has served its purpose of setting callbacks and can not be used anymore.
    A function that executes asynchronously can return the async_result instance. The caller of the function may use the async_result object to set the callbacks for a success and failure notification. When the asynchronous operation is resolved, the respective callbacks are invoked.

If the async_result is resolved by the time the caller sets the callbacks, the callbacks are invoked immediately on the caller's thread. Otherwise, the async_result is pending and the callbacks are invoked when the async operation finishes. It is forbidden to set callbacks on the finalized async_result.

The async_result interface allows setting the value of the callback (also called the result callback) and the local error callback. The result callback is invoked when the asynchronous operation changes state from pending to resolved with success. If the async operation generates errors and fails, the local error callback is invoked instead.

Setting the value callback constitutes chaining a new async operation. Even if the callback itself works fully synchronously, a new async_result object is created. If the value callback returns another async_result, the async operation created by chaining is resolved when the async operation returned by the value callback is resolved. Otherwise, the async operation created by chaining is resolved immediately after the value callback returns.

The consumer of the async_result object can also set the consume errors callback which is mutually exclusive with the value and local error callbacks. If the previous async operation in the chain resolves with failure, the consume errors callback is invoked. It receives the produced error and must return a placeholder or a substitute value of the type of the failed async operation. This value is passed down the chain to the next async operation as if it was successfully generated by the async operation that generated the error. If the previous async operation does not fail, its generated value is passed down the chain and the consume errors callback is not invoked.

If the async operation resolves with an error, the subsequent async operations get their local error callbacks invoked in the proper order, one by one, up to the async operation with the set consume errors callback or the final error callback. This mechanism is called error propagation. Note that the error propagation is synchronous and no error callbacks can return async_result types. There is also no backtracking; generating errors in the chain does not allow going back to the previous async operations and performing any rollback. These mechanisms, if needed, should be implemented by the user in terms of uni-directional chains of operations.

The final error callback is the last of the callbacks which can be set on the async_result. If set, the error callback ends the operations chain. This callback is invoked when the error is generated by or propagated to the last async operation. In case of the successful resolution of the last async operation in the chain, the final error callback is not invoked.

The async_result interface does not provide any possibility of aborting or cancelling the ongoing asynchronous operation. The interface also does not allow modifying the already constructed async operations chain. Such features, if needed, should be implemented by the user in terms of adding hooks in the value callbacks.

#include <async_result_detail.h>

Public Functions

Name
~async_result()
The destructor.
template <typename U >
auto
then(U && cb, err_cb_type && local_err_cb ={})
Sets function objects as callbacks on the asynchronous completion.
async_result< T, traits > &operator=(async_result< T, traits > && other)
The move assignment operator.
operator bool() const
Checks if the async_result is not finalized.
voidon_error(err_cb_type && err_cb)
Sets the final error callback on the async_result.
async_result_with_solver< T, traits >make()
Constructs an async_result paired with the solver.
autoconsume_errors(typename traits::template callback_type< T(std::exception_ptr &&)> && err_cb)
Consumes errors produced during an asynchronous operation to not propagate the errors further.
async_result(low_level_solver_ptr< T, traits > on_result, async_result_tags::low_level_tag )
A constructor that is used by the asynchronous operation initiator.
async_result(async_result< T, traits > && other)
The move constructor.
template <typename TT =T,typename X =std::enable_if_t<!std::is_same_v<TT, void>, T>>
async_result(TT && val, async_result_tags::resolved_tag =async_result_tags::resolved)
Constructs the resolved result.
template <typename TT =T,typename X =std::enable_if_t<std::is_same_v<TT, void>, T>>
async_result()
Constructs the resolved result.
async_result(std::exception_ptr && e, async_result_tags::failed_tag =async_result_tags::failed)
Constructs the failed result.

Friends

Name
structwrap_as_solver
template <typename T,
typename traits>
class dolbyio::comms::detail::async_result;

Public Functions Documentation

function ~async_result

inline ~async_result()

The destructor.

The async_result object may be destroyed only after changing state to finalized.

function then

template <typename U >
inline auto then(
    U && cb,
    err_cb_type && local_err_cb ={}
)

Sets function objects as callbacks on the asynchronous completion.

Parameters:

  • cb The result callback function object.
  • local_err_cb The local error function object. This functor should not throw any exceptions.

Return: The async_result representing the result of the callback invocation.

If the async_result is in the pending state, then when the asynchronous operation finishes, depending on the result, one of the passed callbacks is invoked. If the async operation finishes successfully, then the result callback is invoked. If the async operation fails, the local error callback is invoked.

If the async_result is already in the resolved state by the time this method is invoked, the result or error callback is executed immediately.

This method consumes the current async_result and leaves the result in the finalized state.

This method constructs a new async_result. The type of the result depends on the return type of the provided result callback:

  • If the provided result callback returns async_result<X, Y>, then this method returns async_result<X, Y>. The async_result returned from then() is resolved when the instance returned by the callback function is resolved.
  • If the provided result callback returns any type Z (may be void), this method returns async_result<Z, traits>. The returned async result is resolved immediately when the callback function is invoked.
    In any case, if the result callback is invoked and throws an exception, then the async_result returned from then() is resolved with failure.
// On success the following code prints `123`.
 method_returns_async_result()
   .then([](auto&&)
     { std::cerr << "1";
       return another_async_method()
              .then([](auto&&){ std::cerr << "2"; }); },
                     [](auto&& e) { // rethrow and handle exception })
              .then([](auto&&){ std::cerr << "3"; },
                    [](auto&& e) { // rethrow and handle exception });
     })
   .on_error([](auto&& e) { // rethrow and handle exception });

function operator=

inline async_result< T, traits > & operator=(
    async_result< T, traits > && other
)

The move assignment operator.

Parameters:

Return: The reference to the async_result.

Moves the argument to the current async_result. The current async_result must be in the finalized state. The move assignment operator leaves the argument in the finalized state.

function operator bool

inline explicit operator bool() const

Checks if the async_result is not finalized.

Return: False if the async_result is finalized, true otherwise.

function on_error

inline void on_error(
    err_cb_type && err_cb
)

Sets the final error callback on the async_result.

Parameters:

  • err_cb The final error function object.

This callback is executed when the async operation resolves with failure. If the async operation resolves with a result, the final error callback is never invoked. If the async_result is already in the resolved with failure state when this function is called, the final error callback is executed immediately.

This method consumes the current async_result and changes its state to finalized.

This method does not return anything and should be invoked to close the async operations chain. In order to handle errors in the middle of the async operations chain, use the local error callbacks or the consume errors callback.

The final error callback is allowed to throw exceptions of its own, but these exceptions are caught and ignored.

 method_returns_async_result().then([](auto&&){ // handle success })
                              .on_error([](auto&& e) { // rethrow and
handle exception });

function make

static inline async_result_with_solver< T, traits > make()

Constructs an async_result paired with the solver.

Return: A pair of the async_result and the associated solver.

We recommend using this method for constructing a new async_result by the initiator of the asynchronous operation. The method returns a pair of objects: the async_result and the associated solver. The async_result object should be returned to the caller, which is the consumer of the asynchronous operation's result. The solver should be passed to the asynchronous code, which eventually resolves the solver when the operation is finished. Resolving the solver changes the state of the async_result to resolved.

 // Enqueues a job on the async operations queue, and returns the
 // async_result:
async_result<void> do_something() { auto [res, solver] =
async_result<void>::make();
   async_operations_queue.emplace_back(std::move(solver));
   return std::move(res);
 }

 // Real implementation of the async operation, executed at some point:
 void do_something_impl() {
   // do stuff

   // Causes the async_result<void> returned from do_something() to resolve
   // and invoke the callbacks:
   std::move(async_operations_queue.front()).resolve();
   async_operations_queue.pop_front();
 }

function consume_errors

inline auto consume_errors(
    typename traits::template callback_type< T(std::exception_ptr &&)> && err_cb
)

Consumes errors produced during an asynchronous operation to not propagate the errors further.

Parameters:

  • err_cb The consume errors function object.

Return: The async_result which is resolved with a value of this result or, in a case of errors, the value returned by the consume errors callback.

The errors are consumed by the provided error callback function object. If the asynchronous operation is resolved with an error, the chained async operations cannot be executed and error callbacks are invoked. By using the consume_errors() call, the error propagation can be stopped. The caller captures the generated error and replaces the error with a result that is propagated to subsequent asynchronous operations. If the callback throws an error, that error is propagated to subsequent async results instead of the captured error. The callback function returns values of type T.

This method consumes the current async_result and changes its state to finalized.

This method constructs a new async_result of the same type as the current async_result's type.

 method_returns_async_result().then([](auto&&){ // handle success })
                              .consume_errors(([]auto&&) { // handle
exception }) .then([](){ return another_async_method(); })
                              .on_error([](auto&&) { // handle exception
});

function async_result

inline async_result(
    low_level_solver_ptr< T, traits > on_result,
    async_result_tags::low_level_tag 
)

A constructor that is used by the asynchronous operation initiator.

Parameters:

We do not recommend constructing the async_result manually using this constructor. The recommended way is to use the make() static method.

function async_result

inline async_result(
    async_result< T, traits > && other
)

The move constructor.

Parameters:

Constructs the async_result by moving the internal state from another async_result. The result passed as an argument is left in the finalized state after the constructor returns.

function async_result

template <typename TT  =T,
typename X  =std::enable_if_t<!std::is_same_v<TT, void>, T>>
inline async_result(
    TT && val,
    async_result_tags::resolved_tag  =async_result_tags::resolved
)

Constructs the resolved result.

Parameters:

  • val The result of the operation

The async_result is brought to the resolved state with the provided value at the moment of construction.

The second argument, the async_result_tags::resolved_tag, has a default value and only a single value can be used. Therefore, the argument can be almost always omitted in code. The only scenario where providing the tag is required is constructing the async_result carrying std::exception_ptr as the result of the operation to distinguish between the failed and resolved cases.

// Returns already-resolved result, carrying value 1:
async_result<int, traits> fun() { return 1; }

// Returs already-resolved result, carrying the exception pointer.
// Note that the result is resolved with success, and the value callback
// set in the then() method will be invoked:
async_result<std::exception_ptr, traits> fun2() {
  return {std::make_exception_ptr(std::runtime_error("")),
          async_result_tags::resolved};
}

function async_result

template <typename TT  =T,
typename X  =std::enable_if_t<std::is_same_v<TT, void>, T>>
inline async_result()

Constructs the resolved result.

The async_result changes state to resolved at the moment of the construction.

function async_result

inline async_result(
    std::exception_ptr && e,
    async_result_tags::failed_tag  =async_result_tags::failed
)

Constructs the failed result.

The async_result changes state to resolved with failure at the moment of the construction.

The second argument, the async_result_tags::failed_tag, has a default value and there's only a single value which can be used, therefore the argument can almost always be omitted in the code. The only scenario where providing the tag is required is constructing the async_result carrying the std::exception_ptr as the result of the operation, to distinguish between the failed and resolved cases.

// Returns already-failed result:
async_result<int, traits> fun() {
  return std::make_exception_ptr(std::runtime_error(""));
}

// Returns already-failed result.
// Note that the result is resolved with failure, and the local error
// callback set in the then() method will be invoked. The error will be
// propagated to the chained results, if any:
async_result<std::exception_ptr, traits> fun2() {
  return {std::make_exception_ptr(std::runtime_error("")),
          async_result_tags::failed};
}

Friends

friend wrap_as_solver

friend struct wrap_as_solver();


Did this page help you?