You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将Boost异步函数封装为协程时返回值异常问题求助

问题描述

尝试将基于Boost、使用完成处理器(completion handlers)的第三方库类封装为Boost协程使用。简化代码中,期望async_connect返回的awaitable类型为boost::system::error_code,但实际返回void。后续修改完成处理器签名,加入std::exception_ptr作为第一个参数后,结果能正确从协程返回,但不理解该签名要求的原因。

原始简化代码:

#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <iostream>

using AsyncHandler = std::function<void(boost::system::error_code)>;

struct LibraryClient
{
    LibraryClient(boost::asio::io_context& ioc)
        : socket{ioc}
    {}
    boost::asio::ip::tcp::socket socket;

    void async_connect(AsyncHandler handler = {})
    {
        boost::system::error_code ec;
        boost::asio::ip::address ip_address = boost::asio::ip::address::from_string("127.0.0.1", ec);
        boost::asio::ip::tcp::endpoint ep(ip_address, 9999);

        socket.async_connect(ep, std::move(handler));
    }
};


template<class CompletitionToken = boost::asio::use_awaitable_t<>>
auto do_async_connect(LibraryClient& client, CompletitionToken&& token = {})
{
    auto initiate = [&client]<class H>(H&& self) mutable 
    {
        client.async_connect([self = std::make_shared<H>(std::forward<H>(self))](auto&& r)
        {
            (*self)(r);
        });
    };

    return boost::asio::async_initiate<
        CompletitionToken, boost::system::error_code(boost::system::error_code)
        >(initiate, token);
}


struct LibraryClientWrapper
{
    LibraryClient client;

    boost::asio::awaitable<boost::system::error_code> async_connect()
    {
        const auto ec = co_await do_async_connect(client);
    }
};


int main()
{
    auto ioc = boost::asio::io_context{};
    auto client = LibraryClientWrapper{LibraryClient{ioc}};

    ioc.run();
}

编辑后的测试代码(验证签名修改有效):

#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <iostream>
#include <cassert>


template<class CompletitionToken>
auto do_async_connect(LibraryClient& client, CompletitionToken&& token)
{
    auto initiate = [](auto&& handler) {
        handler(nullptr, 90);
    };

    return boost::asio::async_initiate<CompletitionToken, void(std::exception_ptr, int)>(
        initiate, std::forward<CompletitionToken>(token)
    );
}


struct LibraryClientWrapper
{
    LibraryClient client;


    boost::asio::awaitable<void> async_connect()
    {
        const auto ec = co_await do_async_connect(client, boost::asio::use_awaitable);
        assert(ec == 90);
    }
};

void rethrow_exception(std::exception_ptr eptr)
{
    if (eptr)
    {
        std::rethrow_exception(eptr);
    }
}

int main()
{
    auto ioc = boost::asio::io_context{};
    auto client = LibraryClientWrapper{LibraryClient{ioc}};
    boost::asio::co_spawn(ioc, client.async_connect(), rethrow_exception);


    ioc.run();
}
问题原因与解决方案

核心原因

Boost Asio的协程机制(基于use_awaitable令牌)要求完成处理器的签名必须遵循特定约定:第一个参数为std::exception_ptr,后续参数为操作的结果值。

这个约定的设计目的是统一处理两种错误传递方式:

  • 如果异步操作通过抛出异常报告错误,Asio会将异常封装为std::exception_ptr传入处理器;
  • 如果操作通过返回错误码(如boost::system::error_code)报告错误,处理器的第一个参数传入nullptr,后续参数传递结果值。

你的原始代码中,async_initiate指定的签名是boost::system::error_code(boost::system::error_code),不符合Asio协程的签名要求,导致Asio无法正确将error_code映射为co_await的返回值,最终awaitable类型退化为void。

解决方案

修改do_async_connect函数中async_initiate的签名,以及调用完成处理器的方式,符合Asio的约定:

template<class CompletitionToken = boost::asio::use_awaitable_t<>>
auto do_async_connect(LibraryClient& client, CompletitionToken&& token = {})
{
    auto initiate = [&client]<class H>(H&& self) mutable 
    {
        client.async_connect([self = std::make_shared<H>(std::forward<H>(self))](boost::system::error_code ec)
        {
            // 第一个参数传nullptr(无异常),第二个参数传实际的error_code
            (*self)(nullptr, ec);
        });
    };

    // 签名改为void(std::exception_ptr, boost::system::error_code)
    return boost::asio::async_initiate<
        CompletitionToken, void(std::exception_ptr, boost::system::error_code)
        >(std::move(initiate), std::forward<CompletitionToken>(token));
}

修改后,LibraryClientWrapper::async_connect中的co_await就能正确获取到boost::system::error_code作为返回值,awaitable类型也会符合预期。

补充说明

编辑后的测试代码中,加入std::exception_ptr作为第一个参数后,int值能正确返回,本质就是符合了Asio协程的签名约定:当第一个参数为nullptr时,co_await会将后续参数作为返回值。


内容的提问来源于stack exchange,提问作者bielu000

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 23:18:42