0%

Write a synchronize API based on ASIO asynchronous operation

前言

基本上這個要求蠻奇怪的,ASIO又不是沒提供Synchronize API,不過有些事情就是只有Asynchronous API能做到
例如我要在五秒鐘之內連線,五秒鐘之內無法連上就直接結束,如果用Synchronize API,Timeout由作業系統決定
這個時候就只有自己寫了

use_future

ASIO有一個feature,可以將Async operation轉成Sync operation
一般來說我們的程式碼會寫成這樣

1
2
3
socket.async_connect(endpoint, [](std::error_code ec) {
// blablabla
});

但是如果我們用use_future的話,ASIO內部會自己轉成promise/future的Pattern
這適合在Threead synchronize的情景使用

1
2
3
4
5
6
7
asio::io_context ctx;
asio::ip::tcp::socket socket(ctx);
auto future = socket.async_connect(endpoint, asio::use_future);
std::thread t([&] {
ctx.run();
});
future.get();

Combie with C++20 Coroutine

如果我們的條件更複雜,如一開始寫的五秒鐘Timeout這件事,上面的程式碼就不敷使用,
如果用原先的Function callback方式寫大概會死一堆腦細胞,而Coroutine可以讓我們大大減輕心智負擔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
asio::awaitable<void> timeout(std::chrono::seconds seconds)
{
asio::steady_timer timer(co_await asio::this_coro::executor);
timer.expires_after(seconds);
co_await timer.async_wait(use_nothrow_awaitable);
}

asio::awaitable<std::error_code> connect_with_timeout(
asio::ip::tcp::socket& socket,
const asio::ip::tcp::endpoint& endpoint)
{
using namespace asio::experimental::awaitable_operators;
auto result = co_await(
socket.async_connect(endpoint, use_nothrow_awaitable) ||
timeout(std::chrono::seconds(5))
);
if (result.index() == 1) {
co_return asio::error::timed_out; // timed out
}
auto [r] = std::get<0>(result);
co_return r;
}

asio::io_context io_context;
auto connect_future = asio::co_spawn(
io_context.get_executor(),
connect_with_timeout(asio::ip::tcp::socket(io_context), endpoint),
asio::use_future);
io_context.run();
return connect_future.get();

如上面程式碼寫的一樣
connect_with_timeout有兩種可能,一個是socket connect的結果,另外一個是timeout
asio::co_spawn的最後一個參數不是教學中的detach,而是剛剛講的use_future
這樣子就可以把Coroutine 和 promise/future一起使用