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

问题描述 投票:0回答:1

我有一个组合的异步操作,它使用非升压 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)
    ,即使其与内置异步操作的工作方式相同? (我相信第一个问题回答完之后就很简单了)

docs 没有提供 async_compose 和协程的示例,也没有提供从

co_spawn
返回值的示例。

c++ boost-asio c++20 coroutine
1个回答
2
投票

我以为我已经尝试过这个,但只需更改

async_compose
模板参数的签名,
self.complete
就可以了。

对于错误,似乎您需要显式捕获

system_error
,将其作为
error_code
传递给完成处理程序,并且可以将其传递给用户的完成处理程序或将其转换回
system_error
类似于
use_future
代币的东西。

正如 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
正是用于此目的。在我看来,它并不比我上面的解决方案简单多少,但它的重量可能更轻。

© www.soinside.com 2019 - 2024. All rights reserved.