如何使用C++20协同程序的组合Asio操作来返回值



我有一个组合异步操作,它使用非boost Asio 1.18.1来解析并连接到主机和服务。

我希望它将连接到的实际端点传递给完成令牌。现在没有。

#include <iostream>
#include <string_view>
#include "asio.hpp"
template <typename Token, typename Executor>
auto async_connect_to(Executor&& executor, asio::ip::tcp::socket& socket,
asio::ip::tcp::resolver& resolver, std::string_view host,
std::string_view service, Token&& token) {
return asio::async_compose<Token, void()>(
[&](auto& self) {
co_spawn(
executor,
[&]() -> asio::awaitable<void> {
auto results = co_await resolver.async_resolve(host, service, asio::use_awaitable);
auto endpoint = co_await asio::async_connect(socket, results, asio::use_awaitable);
self.complete();
co_return;
},
asio::detached);
},
token, executor);
}
int main(int argc, char* argv[]) {
if (argc != 3) {
std::cerr << "Usage: client <host> <port>n";
return 1;
}
asio::io_context io_context;
asio::ip::tcp::socket socket(io_context);
asio::ip::tcp::resolver resolver(io_context);
async_connect_to(io_context, socket, resolver, argv[1], argv[2],
[](asio::ip::tcp::endpoint endpoint = {}) {
std::cout << "connected to: " << endpoint << "n";
});
try {
io_context.run();
} catch (std::exception& e) {
std::cerr << "error: " << e.what() << "n";
return 1;
}
return 0;
}

因为我在做self.complete(),所以main中的完成处理程序实际上从未得到我们连接的端点,所以它是默认的。

  1. 如何从这个组合的异步操作中实际返回值
  2. 如何改进错误处理,使完成处理程序可以使用签名(error_code, tcp::endpoint)而不仅仅是(tcp::endpoint),即使其以与内置异步操作相同的方式工作?(我相信在回答完第一个问题后,这很简单(

文档没有提供async_compose和协同程序的示例,也没有提供从co_spawn返回值的示例。

我以为我已经尝试过了,但只需更改async_compose模板参数和self.complete的签名就可以了。

对于错误,似乎需要显式捕获system_error,将其作为error_code传递给完成处理程序,然后它可能被传递给用户的完成处理程序或用类似use_future的令牌转换回system_error

正如Andrew在评论中所建议的那样,我还将捕获self移到协程中,并使lambda可变。

template <typename Token, typename Executor>
auto async_connect_to(Executor&& executor, asio::ip::tcp::socket& socket,
asio::ip::tcp::resolver& resolver, std::string_view host,
std::string_view service, Token&& token) {
return asio::async_compose<Token,
void(std::error_code, asio::ip::tcp::endpoint)>(
[&](auto& self) {
co_spawn(
executor,
[&, self = std::move(self)]() mutable -> asio::awaitable<void> {
try {
auto results = co_await resolver.async_resolve(
host, service, asio::use_awaitable);
auto endpoint = co_await asio::async_connect(
socket, results, asio::use_awaitable);
self.complete({}, endpoint);
} catch (std::system_error& e) {
self.complete(e.code(), {});
}
co_return;
},
asio::detached);
},
token, executor);
}

这似乎像这样工作得很好:

async_connect_to(
io_context, socket, resolver, argv[1], argv[2],
[](std::error_code ec, asio::ip::tcp::endpoint endpoint = {}) {
if (ec) {
std::cout << "error: " << ec.message() << "n";
} else {
std::cout << "connected to: " << endpoint << "n";
}
});

与期货:

auto f = async_connect_to(io_context, socket, resolver, argv[1], argv[2], asio::use_future);
try {
auto endpoint = f.get();
std::cout << "connected to: " << endpoint << "n";
} catch (std::exception& e) {
std::cerr << "error: " << e.what() << "n";
}

编辑:从Asio 1.25.0开始,似乎还有asio::experimental::co_composed,正是为了这个目的。它看起来并不比我上面的解决方案IMO简单多少,但它可能更轻。

最新更新