...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
Note | |
---|---|
These type requirements and classes are the low level building blocks
of cancellation. For most use cases, consider using a higher level abstraction,
such as experimental::make_parallel_group
or the logical
operators for |
I/O objects, such as sockets and timers, support object-wide cancellation
of outstanding asynchronous operations via their close
or cancel
member functions.
However, certain asynchronous operations also support individual, targeted
cancellation. This per-operation cancellation is enabled by specifying
that a completion handler has an associated
cancellation slot which satisfies the CancellationSlot
type requirements. A cancellation slot is a lightweight channel used for
delivering a cancellation request.
Given a copy of a user-defined Handler
object h
, if an asynchronous
operation supports cancellation it will obtain a cancellation slot using
the get_associated_cancellation_slot
function. For example:
boost::asio::associated_cancellation_slot_t<Handler> s = boost::asio::get_associated_cancellation_slot(h);
The associated cancellation slot must satisfy the CancellationSlot type requirements.
By default, handlers use a default-constructed cancellation_slot
, which means
that per-operation cancellation is disabled. The cancellation slot may
be customised for a particular handler type by specifying a nested type
cancellation_slot_type
and member function get_cancellation_slot()
:
class my_handler { public: // Custom implementation of CancellationSlot type requirements. typedef my_cancellation_slot cancellation_slot_type; // Return a custom cancellation slot implementation. cancellation_slot_type get_cancellation_slot() const noexcept { return my_cancellation_slot(...); } void operator()() { ... } };
In more complex cases, the associated_cancellation_slot
template may be partially specialised directly:
namespace boost { namespace asio { template <typename CancellationSlot> struct associated_cancellation_slot<my_handler, CancellationSlot> { // Custom implementation of CancellationSlot type requirements. typedef my_cancellation_slot type; // Return a custom cancellation_slot implementation. static type get(const my_handler&, const CancellationSlot& a = CancellationSlot()) noexcept { return my_cancellation_slot(...); } }; } } // namespace boost::asio
For convenience, a cancellation slot may be associated with a handler by
using the bind_cancellation_slot
function.
This is particularly useful when associating a cancellation slot with a
lambda:
boost::asio::async_read(my_socket, my_buffer, boost::asio::bind_cancellation_slot( my_cancellation_slot, [](boost::system::error_code e, std::size_t n) { ... } ) );
Boost.Asio provides a ready-to-use cancellation slot in the form of cancellation_slot
and its counterpart cancellation_signal
. These two
classes implement a one-to-one pairing of producer (signal) and consumer
(slot) interfaces. The following example shows its use:
class session : public std::enable_shared_from_this<proxy> { ... void do_read() { auto self = shared_from_this(); socket_.async_read_some( buffer(data_), boost::asio::bind_cancellation_slot( cancel_signal_.slot(), [self](boost::system::error_code error, std::size_t n) { ... } ) ); } ... void request_cancel() { cancel_signal_.emit(boost::asio::cancellation_type::total); } ... boost::asio::cancellation_signal cancel_signal_; };
A cancellation_signal
contains
a single slot, and consequently a cancellation signal/slot pair may be
used with at most one operation at a time. However, the same slot may be
reused for subsequent operations.
To support cancellation, an asynchronous operation installs a cancellation
handler into the slot by calling the slot's assign
or emplace
functions. This
handler will be invoked when a cancellation signal is emitted. A slot holds
exactly one handler at a time, and installing a new handler will overwrite
any previously installed handler.
When emitting a cancellation signal, the caller must specify a cancellation type. This value is a bitmask that dictates what guarantees the cancellation target must make if successful cancellation occurs. The possible bit values are, from weakest to strongest guarantee, are:
Table 1. cancellation types
Bit |
Guarantee if cancellation is successful |
Examples where this is the strongest supported guarantee |
---|---|---|
|
The operation had unspecified side effects, and it is only safe to close or destroy the I/O object. |
A stateful implementation of a message framing protocol, where an asynchronous operation sends or receives a complete message. If cancellation occurs part-way through the message body, it is not possible to report a sensible state to the completion handler. |
|
The operation had well-defined side effects, and the completion handler for the operation indicates what these side effects were. |
Composed operations such as |
|
The operation had no side effects that are observable through the API. |
Low level system calls that transfer either zero or non-zero
bytes. |
For example, if application logic requires that an operation complete with
all-or-nothing side effects, it should emit only the total
cancellation type. If this type is unsupported by the target operation,
no cancellation will occur.
Furthermore, a stronger guarantee always satisfies the requirements of
a weaker guarantee. The partial
guarantee still satisfies the terminal
guarantee. The total
guarantee
satisfies both partial
and terminal
. This means
that when an operation supports a given cancellation type as its strongest
guarantee, it should honour cancellation requests for any of the weaker
guarantees.
Cancellation requests should not be emitted during an asynchronous operation's initiating function. Cancellation requests that are emitted before an operation starts have no effect. Similarly, cancellation requests made after completion have no effect.
When emitting a cancellation signal, the thread safety rules apply as if calling a member function on the target operation's I/O object. For non-composed operations, this means that it is safe to emit the cancellation signal from any thread provided there are no other concurrent calls to the I/O object, and no other concurrent cancellation signal requests. For composed operations, care must be taken to ensure the cancellation request does not occur concurrently with the operation's intermediate completion handlers.
Consult the documentation for individual asynchronous operations for their supported cancellation types, if any. The ability to cancel individual operations, or composed operations, is currently supported by:
async_read
and async_write
async_compose
awaitable
experimental::coro
experimental::parallel_group
operation
experimental::promise
class
CancellationSlot, associated_cancellation_slot, bind_cancellation_slot, cancellation_signal, cancellation_slot, cancellation_state, cancellation_type, get_associated_cancellation_slot, experimental::parallel_group, experimental::make_parallel_group