async_patterns::DispatcherBound

#include <dispatcher_bound.h>

|DispatcherBound| enables an owner object living on some arbitrary thread, to construct, call methods on, and destroy an object of type |T| that must be used from a particular [synchronized async dispatcher][synchronized-dispatcher].

Summary

Thread-unsafe asynchronous types should be used from synchronized dispatchers (e.g. a single-threaded async loop). Because the dispatcher may be running code to manipulate such objects, one should not use the same objects from other unrelated threads and cause data races.

However, it may not always be possible for an entire tree of objects to live on the same async dispatcher, due to design or legacy constraints. |DispatcherBound| helps one divide classes along dispatcher boundaries.

An example:

// |Background| always lives on a background dispatcher, provided
// at construction time.
class Background {
 public:
  explicit Background() {
    // Perform some asynchronous work. The work is canceled if
    // |Background| is destroyed.
    task_.Post(async_get_default_dispatcher());
  }

 private:
  void DoSomething();

  // |task_| manages an async task that borrows the containing
  // |Background| object and is not thread safe. It must be destroyed
  // on the dispatcher to ensure that task cancellation is not racy.
  async::TaskClosureMethod<Background, &Background::DoSomething> task_{this};
};

class Owner {
 public:
  // Asynchronously constructs a |Background| object on its dispatcher.
  // Code in |Owner| and code in |Background| may run concurrently.
  //
  // The dispatcher will not be attached to the current thread, but will
  // be attached to the loop thread. This way, the |Background| object
  // can obtain a dispatcher from its constructor using
  // |async_get_default_dispatcher|.
  explicit Owner() :
      background_loop_(&kAsyncLoopConfigNoAttachToCurrentThread),
      background_{background_loop_.dispatcher(), std::in_place} {}

 private:
  // The async loop which will manage |Background| objects.
  // This will always be paired with a |DispatcherBound| object.
  async::Loop background_loop_;

  // The |DispatcherBound| which manages |Background| on its loop.
  // During destruction, |background_| will schedule the asynchronous
  // destruction of the wrapped |Background| object on the dispatcher.
  async_patterns::DispatcherBound background_;
};

|DispatcherBound| itself is thread-compatible.

Safety of sending arguments

When constructing |T| and calling member functions of |T|, it is possible to pass additional arguments if the constructor or member function requires it. The argument will be forwarded from the caller's thread into a heap data structure, and later moved into the thread which would run the dispatcher task asynchronously. Each argument must be safe to send to a different thread. See |async_patterns::BindForSending| for the detailed requirements.

Inheritance

Direct Known Subclasses:async_patterns::TestDispatcherBound< T >

Constructors and Destructors

DispatcherBound(async_dispatcher_t *dispatcher, std::in_place_t, Args &&... args)
Arguments after |std::in_place| are sent to the constructor of |T|.
DispatcherBound(async_dispatcher_t *dispatcher)
Constructs a |DispatcherBound| that does not hold an instance of |T|.
DispatcherBound(DispatcherBound &&)
Typically, asynchronous classes would contain internal self-pointers that make moving dangerous, so we disable moves here for now.
DispatcherBound(const DispatcherBound &)
~DispatcherBound()
If |has_value|, asynchronously destroys the managed |T| on a task posted to the dispatcher.

Public functions

AsyncCall(Member T::*member, Args &&... args)
auto
Asynchronously calls |member|, a pointer to member function of |T|, using the provided |args|.
emplace(Args &&... args)
void
Asynchronously constructs |T| on a task posted to the dispatcher.
has_value() const
bool
Returns if this object holds an instance of |T|.
operator=(DispatcherBound &&) noexcept=delete
operator=(const DispatcherBound &) noexcept=delete
reset()
void
If |has_value|, asynchronously destroys the managed |T| on a task posted to the dispatcher.

Protected functions

CheckArgs(fit::parameter_pack< Args...>)
constexpr void
UnsafeAsyncCallImpl(Callable && callable, Args &&... args)
auto
Calls an arbitrary |callable| asynchronously on the |dispatcher_|.

Public functions

AsyncCall

auto AsyncCall(
  Member T::*member,
  Args &&... args
)

Asynchronously calls |member|, a pointer to member function of |T|, using the provided |args|.

|AsyncCall| returns a |PendingCall| object that lets you asynchronously monitor the result. You may either:

  • Make a fire-and-forget call, by discarding the returned object, or
  • Get a promise carrying the return value of the function by calling promise() on the object, yielding a |fpromise::promise|, or
  • Call Then() on the object and pass a |Callback|.

See |PendingCall| for details.

In particular, if |member| returns void, you could attach promises/callbacks that take void to asynchronously get notified when |member| has finished execution.

Example:

class Owner {
 public:
  Owner(async_dispatcher_t* owner_dispatcher) : receiver_{this, owner_dispatcher} {
    background_.emplace();
    // Tell |background_| to |DoSomething|, then send back the return
    // value to |Owner| using |receiver_|.
    background_
        .AsyncCall(&Background::DoSomething)
        .Then(receiver_.Once(&Owner::DoneSomething));
  }

  void DoneSomething(Result result) {
    // |Background::DoSomething| has completed with |result|...
  }

 private:
  async::Loop background_loop_;
  async_patterns::DispatcherBound background_{background_loop_.dispatcher()};
  async_patterns::Receiver receiver_;
};

See |async_patterns::BindForSending| for detailed requirements on |args|.

If |Background::DoSomething| is an overloaded member function, you may disambiguate it by spelling out its signature:

background_.AsyncCall<void(Result)>(&Background::DoSomething);

The task will be synchronously called if the dispatcher is shutdown.

DispatcherBound

 DispatcherBound(
  async_dispatcher_t *dispatcher,
  std::in_place_t,
  Args &&... args
)

Arguments after |std::in_place| are sent to the constructor of |T|.

See |async_patterns::BindForSending| for detailed requirements on |args|.

If you'd like to pass a |dispatcher| to |T| as a constructor argument, see |async_patterns::PassDispatcher|.

If the dispatcher is shutdown, |T| will be synchronously constructed.

DispatcherBound

 DispatcherBound(
  async_dispatcher_t *dispatcher
)

Constructs a |DispatcherBound| that does not hold an instance of |T|.

One may later construct |T| using |emplace| on the |dispatcher|.

DispatcherBound

 DispatcherBound(
  DispatcherBound &&
) noexcept=delete

Typically, asynchronous classes would contain internal self-pointers that make moving dangerous, so we disable moves here for now.

DispatcherBound

 DispatcherBound(
  const DispatcherBound &
) noexcept=delete

emplace

void emplace(
  Args &&... args
)

Asynchronously constructs |T| on a task posted to the dispatcher.

If this object already holds an instance of |T|, that older instance will be asynchronously destroyed on the dispatcher.

If |T2| is specified, it must be same as |T| or a subclass. Then an instance of |T2| will be constructed. This can be useful for mocking: |T| may be some interface, and when constructing the object, either a fake (in unit tests) or a real concrete type (in production) will be specified.

If you'd like to pass a |dispatcher| to |T| as a constructor argument, see |async_patterns::PassDispatcher|.

See |async_patterns::BindForSending| for detailed requirements on |args|.

has_value

bool has_value() const 

Returns if this object holds an instance of |T|.

operator=

DispatcherBound & operator=(
  DispatcherBound &&
) noexcept=delete

operator=

DispatcherBound & operator=(
  const DispatcherBound &
) noexcept=delete

reset

void reset()

If |has_value|, asynchronously destroys the managed |T| on a task posted to the dispatcher.

If the dispatcher is shutdown, |T| will be synchronously destroyed.

~DispatcherBound

 ~DispatcherBound()

If |has_value|, asynchronously destroys the managed |T| on a task posted to the dispatcher.

If the dispatcher is shutdown, |T| will be synchronously destroyed.

Protected functions

CheckArgs

constexpr void CheckArgs(
  fit::parameter_pack< Args...>
)

UnsafeAsyncCallImpl

auto UnsafeAsyncCallImpl(
  Callable && callable,
  Args &&... args
)

Calls an arbitrary |callable| asynchronously on the |dispatcher_|.