あめだまふぁくとりー

Boost.Graphとかできますん

initiating function 内で yield_context を呼べるようになった

Boost 1.58.0 以降では, Asynchronous operation の initiating function (async_write_some など) 内で直接 yield_context の handler の呼び出しが可能になりました.

以前まではどうだったかというと,

の順番で処理が実行される必要がありました.

例えば, io_service::dispatch は内部で handler を呼び出す可能性があるので上記の順番と逆の処理になります. そのため, この関数に yield_context を渡すのは今までは未定義動作でしたが, 1.58.0 からは問題なく動作します (そもそもこの関数に yield_context を渡すこと自体意味はないですが).

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>

namespace asio = boost::asio;

int main(int argc, char const* argv[])
{
    asio::io_service io_service{};
    asio::spawn(io_service, [&](asio::yield_context yield) {
        io_service.dispatch(yield); // OK from Boost 1.58.0
    });
    io_service.run();
    return 0;
}

この変更によって, 今まで理論上コンテキストスイッチを必要としなかった部分を省略するといった最適化が可能になります (逆にいうと今までは必ずコンテキストスイッチが発生していました).

// ブロックなしで書き込みが可能な場合や,
// コネクションが Abort している場合等は
// コンテキストスイッチは必要無い.
socket.async_write_some(bufs, yield[ec]);

ただし, Asyncronous Operation の要求 では initiating function 内での handler の呼び出しを禁じているので, 今後このような最適化が行われるかはわかりません.

Boost Asio のドキュメントより一部抜粋.

When an asynchronous operation is complete, the handler for the operation will be invoked as if by:

Constructing a bound completion handler bch for the handler, as described below.
Calling ios.post(bch) to schedule the handler for deferred invocation, where ios is the associated io_service.

This implies that the handler must not be called directly from within the initiating function, even if the asynchronous operation completes immediately.

もしかしたら, asio_handler_is_continuation のような helper 関数を使用して実現するといったことも考えられます.