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

C++20协程中CompletionHandler未调用也未存储的行为探究

问题描述

在使用Boost.Asio的CompletionToken机制时,我原本设计由CompletionToken生成的completion_handler最终会被调用。但我好奇如果completion_handler既不被调用也不被存储时的行为——此时completion_handler会被直接销毁。

我使用C++20协程结合use_awaitable CompletionToken进行了测试,观察到特殊行为:执行co_await后进入completion_handler销毁路径,栈展开,main()中的ioc.run()结束,且过程中未捕获到任何异常。请问这是预期行为还是未定义行为?如果是预期行为,相关文档在哪里?目前我尚未找到。

测试代码

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

namespace as = boost::asio;


template <typename CompletionToken>
auto func(bool call, CompletionToken&& token) {
    auto init = 
        []
        (
            auto completion_handler,
            bool call
        ) {
            if (call) {
                std::move(completion_handler)();
            }
            else {
                // What is happend if completion_handler is neither invoked nor stored ?
            }
        };

    return as::async_initiate<
        CompletionToken,
        void()
    >(
        init,
        token,
        call
    );
}

struct trace {
    trace()  { std::cout << __PRETTY_FUNCTION__ << std::endl; }
    ~trace() { std::cout << __PRETTY_FUNCTION__ << std::endl; }
};

as::awaitable<void> proc1() {
    trace t; // destructed correctly
    try {
        {
            std::cout << "before call=true" << std::endl;
            co_await func(true, as::use_awaitable);
            std::cout << "after  call=true" << std::endl;
            
        }
        {
            std::cout << "before call=false" << std::endl;
            co_await func(false, as::use_awaitable);
            // the following part is never executed
            std::cout << "after  call=false" << std::endl;
        }
    }
    catch (...) {
        std::cout << "caught exception" << std::endl;
    }
    std::cout << "co_return" << std::endl;
    co_return;
}

as::awaitable<void> proc2() {
    for (int i = 0; i != 2; ++i) {
        std::cout << "before proc1" << std::endl;
        co_await proc1();
        std::cout << "after  proc1" << std::endl;
    }
}

int main() {
    as::io_context ioc;
    as::co_spawn(ioc.get_executor(), proc2, as::detached);
    ioc.run();
    std::cout << "finish" << std::endl;
}

输出结果

before proc1
trace::trace()
before call=true
after  call=true
before call=false
trace::~trace()
finish

结论与说明

这种行为属于未定义行为。Boost.Asio的设计契约要求:所有基于CompletionToken的异步操作,必须最终调用生成的completion_handler。对于use_awaitable这类绑定协程的CompletionToken来说,未调用completion_handler就销毁它,会直接终止对应的协程(无异常抛出),导致栈展开;由于没有剩余待处理的异步任务,io_context.run()会直接退出。

Boost.Asio的官方文档中,虽然没有直接针对该场景的明确描述,但在CompletionToken的核心模型说明中隐含了这一要求:异步操作的完成回调是整个异步流程的必要环节,跳过调用会破坏异步操作的状态一致性,引发未定义行为。

实际使用的正确方案

实际开发中,应避免这种未调用回调的情况,建议采用带错误码的回调设计,明确告知调用方操作结果:

template <typename CompletionToken>
auto func(bool call, CompletionToken&& token) {
    auto init = 
        []
        (
            auto completion_handler,
            bool call
        ) {
            if (call) {
                std::move(completion_handler)(boost::system::error_code{});
            }
            else {
                // invoke with error code
                std::move(completion_handler)(
                    boost::system::errc::make_error_code(
                        boost::system::errc::operation_canceled 
                    )
                );
            }
        };

    return as::async_initiate<
        CompletionToken,
        void(boost::system::error_code const&)
    >(
        init,
        token,
        call
    );
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 11:02:04