Every asynchronous agent has an associated executor.
An agent's executor determines how the agent's completion handlers are
queued and ultimately run.
Example uses of executors include:
Coordinating a group of asynchronous agents that operate on shared
data structures, ensuring that the agents' completion handlers never
Ensuring that agents are run on specified execution resource (e.g.
a CPU) that is proximal to data or an event source (e.g. a NIC).
Denoting a group of related agents, and so enabling dynamic thread
pools to make smarter scheduling decisions (such as moving the agents
between execution resources as a unit).
Queuing all completion handlers to run on a GUI application thread,
so that they may safely update user interface elements.
Returning an asynchronous operation's default executor as-is, to run
completion handlers as close as possible to the event that triggered
the operation's completion.
Adapting an asynchronous operation's default executor, to run code
before and after every completion handler, such as logging, user authorisation,
or exception handling.
Specifying a priority for an asynchronous agent and its completion
The asynchronous operations within an asynchronous agent use the agent's
associated executor to:
Track the existence of the work that the asynchronous operation represents,
while the operation is outstanding.
Enqueue the completion handler for execution on completion of an operation.
Ensure that completion handlers do not run re-entrantly, if doing so
might lead to inadvertent recursion and stack overflow.
Thus, an asynchronous agent's associated executor represents a policy of
how, where, and when the agent should run, specified as a cross-cutting
concern to the code that makes up the agent.