The asio::config class provides access to configuration variables that
are associated with an execution context. The class is intended for use
by asio internals, or by libraries or user-provided abstractions that
build on top of asio. These configuration variables will typically be
used to fine tune behaviour, such as enabling or disabling certain
optimisations.
When constructing an execution context, such as an io_context, the
caller may optionally pass a service_maker to install a concrete
configuration service into the context. For example:
asio::io_context ctx{asio::config_from_env{}};
The configuration variables' values are accessed by using the
asio::config class, passing a section, key and default value:
asio::config cfg{ctx};
bool enable_locking = cfg.get("scheduler", "locking", true);
The initial set of configuration variables recognised by the asio
internals correspond to the concurrency hint and its special values:
"scheduler" / "concurrency_hint" (int)
"scheduler" / "locking" (bool)
"reactor" / "registration_locking" (bool)
"reactor" / "io_locking" (bool)
A service_maker is an object that is passed to an execution context's
constructor, and allows services to be added at context construction
time. Additional constructor overloads have been added to io_context and
thread_pool that accept a service_maker. For example:
class my_service_maker : public execution_context::service_maker
{
public:
void make(execution_context& ctx) override
{
make_service<my_service>(ctx);
}
};
io_context ctx{my_service_maker{}};
Added new overloads of experimental::make_parallel_group that may be used
to launch a dynamically-sized set of asynchronous operations, where all
operations are the same type. For example:
using op_type = decltype(
socket1.async_read_some(
boost::asio::buffer(data1),
boost::asio::deferred
)
);
std::vector<op_type> ops;
ops.push_back(
socket1.async_read_some(
boost::asio::buffer(data1),
boost::asio::deferred
)
);
ops.push_back(
socket2.async_read_some(
boost::asio::buffer(data2),
boost::asio::deferred
)
);
boost::asio::experimental::make_parallel_group(ops).async_wait(
boost::asio::experimental::wait_for_all(),
[](
std::vector<std::size_t> completion_order,
std::vector<boost::system::error_code> e,
std::vector<std::size_t> n
)
{
for (std::size_t i = 0; i < completion_order.size(); ++i)
{
std::size_t idx = completion_order[i];
std::cout << "socket " << idx << " finished: ";
std::cout << e[idx] << ", " << n[idx] << "\n";
}
}
);
Thanks go to Klemens Morgenstern for supplying part of this implementation.
The consign completion token adapter can be used to attach additional
values to a completion handler. This is typically used to keep at least
one copy of an object, such as a smart pointer, alive until the
completion handler is called.
For example:
auto timer1 = std::make_shared<boost::asio::steady_timer>(my_io_context);
timer1->expires_after(std::chrono::seconds(1));
timer1->async_wait(
boost::asio::consign(
[](boost::system::error_code ec)
{
// ...
},
timer1
)
);
auto timer2 = std::make_shared<boost::asio::steady_timer>(my_io_context);
timer2->expires_after(std::chrono::seconds(30));
std::future<void> f =
timer2->async_wait(
boost::asio::consign(
boost::asio::use_future,
timer2
)
);
The completion_signature_of trait (and corresponding type alias
completion_signature_of_t) may be used to determine the completion
signature of an asynchronous operation. For example:
auto d = my_timer.async_wait(asio::deferred);
using sig = asio::completion_signature_of<decltype(d)>::type;
// sig is void(error_code)
or with a handcrafted asynchronous operation:
struct my_async_op
{
asio::ip::tcp::socket& socket_ = ...;
template <typename Token>
auto operator()(asio::const_buffer data, Token&& token)
{
return asio::async_write(socket_, data,
std::forward<Token>(token));
}
};
using sig =
asio::completion_signature_of<
my_async_op, asio::const_buffer>::type;
// sig is void(error_code, size_t)
The is_async_operation trait may be used to determine if a function
object, and optional arguments, may be called to initiate an
asynchronous operation. For example, when using asio::deferred
auto d = my_timer.async_wait(asio::deferred);
static_assert(asio::is_async_operation<decltype(d)>::value);
or with a handcrafted asynchronous operation:
struct my_async_op
{
asio::ip::tcp::socket& socket_ = ...;
template <typename Token>
auto operator()(asio::const_buffer data, Token&& token)
{
return asio::async_write(socket_, data,
std::forward<Token>(token));
}
};
static_assert(
asio::is_async_operation<
my_async_op, asio::const_buffer>::value);
This is no longer an experimental facility. The names deferred and
deferred_t have been temporarily retained as deprecated entities under
the asio::experimental namespace, for backwards compatibility.
This is no longer an experimental facility. The names prepend and
prepend_t have been temporarily retained as deprecated entities under
the asio::experimental namespace, for backwards compatibility.
This is no longer an experimental facility. The names append and
append_t have been temporarily retained as deprecated entities under
the asio::experimental namespace, for backwards compatibility.
This is no longer an experimental facility. The names as_tuple and
as_tuple_t have been temporarily retained as deprecated entities under
the asio::experimental namespace, for backwards compatibility.
This adds experimental::channel and experimental::concurrent_channel.
Channels may be used to send completions as messages. For example:
// Create a channel with no buffer space.
channel<void(error_code, size_t)> ch(ctx);
// The call to try_send fails as there is no buffer
// space and no waiting receive operations.
bool ok = ch.try_send(asio::error::eof, 123);
assert(!ok);
// The async_send operation blocks until a receive
// operation consumes the message.
ch.async_send(asio::error::eof, 123,
[](error_code ec)
{
// ...
});
// The async_receive consumes the message. Both the
// async_send and async_receive operations complete
// immediately.
ch.async_receive(
[](error_code ec, size_t n)
{
// ...
});
The mutable_registered_buffer and const_registered_buffer classes are
buffer sequence types that represented registered buffers. These buffers
are obtained by first performing a buffer registration:
auto my_registration =
asio::register_buffers(
my_execution_context,
my_buffer_sequence);
The registration object must be maintained for as long as the buffer
registration is required. The supplied buffer sequence represents the
memory location or locations that will be registered, and the caller
must ensure they remain valid for as long as they are registered. The
registration is automatically removed when the registration object is
destroyed. There can be at most one active registration per execution
context.
The registration object is a container of registered buffers. Buffers
may be obtained from it by iterating over the container, or via direct
index access:
asio::mutable_registered_buffer my_buffer
= my_registration[i];
The registered buffers may then be passed directly to operations:
asio::async_read(my_socket, my_buffer,
[](error_code ec, size_t n)
{
// ...
});
Buffer registration supports the io_uring backend when used with read
and write operations on descriptors, files, pipes, and sockets.