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

如何在Boost.Asio+C++20协程中实现RAII式可靠清理?

Boost.Asio C++20协程的可靠RAII式清理实现

背景与现有尝试

手动清理的可行版本(非RAII)

通过在协程中编写try-catch确保清理逻辑始终执行,这是最直接的方式,但需要重复编写异常处理代码:

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

namespace asio = boost::asio;

asio::awaitable<void> test_setup() {
    std::cout << "setup" << std::endl;
    co_return;
}

asio::awaitable<void> test_body() {
    std::cout << "body" << std::endl;
    throw 1; // 模拟测试代码抛出异常
    co_return;
}

asio::awaitable<void> test_cleanup() {
    std::cout << "cleanup" << std::endl;
    co_return;
}

asio::awaitable<void> test_launcher() {
    co_await test_setup();

    try {
        co_await test_body();
    }
    catch (...) {
        std::cout << "caught exception" << std::endl;
    }

    // 始终执行清理
    co_await test_cleanup();
    co_return;
}

int main() {
    asio::io_context ioc;
    asio::co_spawn(
        ioc.get_executor(),
        test_launcher,
        asio::detached
    );
    ioc.run();
}

输出:

setup
body
caught exception
cleanup

移除try-catch的尝试(存在缺陷)

尝试将异常处理移到co_spawn的完成回调中,但这样会导致test_body抛出异常时,test_cleanup无法执行:

asio::co_spawn(
    ioc.get_executor(),
    test_launcher,
    [](std::exception_ptr ep) {
        if (ep) {
            try {
                std::rethrow_exception(ep);
            }
            catch (...) {
                std::cout << "caught exception" << std::endl;
            }
        }
    }
);

作用域守卫临时方案(仍有不足)

用带自定义删除器的shared_ptr实现RAII,但删除器无法直接co_await,只能启动detached清理协程,导致无法等待清理完成:

asio::awaitable<void> test_launcher() {
    co_await test_setup();
    auto exe = co_await asio::this_coro::executor;

    std::shared_ptr<void> scope_guard{
        nullptr,
        [exe](void*) {
            std::cout << "scope_guard called" << std::endl;
            asio::co_spawn(
                exe,
                test_cleanup,
                asio::detached
            );
        }
    };

    co_await test_body();  // 抛出异常
    co_return;
}

输出:

setup
body
scope_guard called
cleanup begin
caught exception
cleanup end

当前方案的问题:

  • 无法在作用域守卫中直接等待清理协程完成
  • detached的清理协程可能在主协程结束后仍在运行,导致资源释放时机不可控

推荐解决方案

方案1:通用with_finally封装(最简方案)

封装一个通用的with_finally函数,接受主逻辑和清理逻辑的awaitable,确保无论主逻辑是否抛出异常,清理逻辑都会被执行并等待完成:

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

namespace asio = boost::asio;

asio::awaitable<void> test_setup() {
    std::cout << "setup" << std::endl;
    co_return;
}

asio::awaitable<void> test_body() {
    std::cout << "body" << std::endl;
    throw 1;
    co_return;
}

asio::awaitable<void> test_cleanup() {
    std::cout << "cleanup begin" << std::endl;
    // 模拟耗时清理操作
    co_await asio::steady_timer(co_await asio::this_coro::executor, std::chrono::milliseconds(100)).async_wait(asio::use_awaitable);
    std::cout << "cleanup end" << std::endl;
    co_return;
}

// 通用finally封装:确保清理逻辑始终执行
template<typename MainAwaitable, typename CleanupAwaitable>
asio::awaitable<void> with_finally(MainAwaitable main_op, CleanupAwaitable cleanup_op) {
    try {
        co_await std::move(main_op);
    } catch (...) {
        // 执行清理后重新抛出异常,让co_spawn回调处理
        co_await std::move(cleanup_op);
        throw;
    }
    // 正常退出时执行清理
    co_await std::move(cleanup_op);
}

asio::awaitable<void> test_launcher() {
    co_await test_setup();
    // 用with_finally包裹主逻辑和清理逻辑
    co_await with_finally(test_body(), test_cleanup());
}

int main() {
    asio::io_context ioc;
    asio::co_spawn(
        ioc.get_executor(),
        test_launcher,
        [](std::exception_ptr ep) {
            if (ep) {
                try {
                    std::rethrow_exception(ep);
                } catch (int e) {
                    std::cout << "caught exception: " << e << std::endl;
                } catch (...) {
                    std::cout << "caught unknown exception" << std::endl;
                }
            }
        }
    );
    ioc.run();
}

输出:

setup
body
cleanup begin
cleanup end
caught exception: 1

这个方案完全避免了重复编写try-catch,同时保证清理逻辑被等待完成,异常也能正确传递到co_spawn的回调中处理。

方案2:自定义可等待作用域守卫(灵活场景)

如果需要更灵活的清理时机控制(比如提前触发清理),可以自定义一个支持co_await的作用域守卫:

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

namespace asio = boost::asio;

asio::awaitable<void> test_setup() {
    std::cout << "setup" << std::endl;
    co_return;
}

asio::awaitable<void> test_body() {
    std::cout << "body" << std::endl;
    throw 1;
    co_return;
}

asio::awaitable<void> test_cleanup() {
    std::cout << "cleanup begin" << std::endl;
    co_await asio::steady_timer(co_await asio::this_coro::executor, std::chrono::milliseconds(100)).async_wait(asio::use_awaitable);
    std::cout << "cleanup end" << std::endl;
    co_return;
}

// 可等待的作用域守卫
template<typename CleanupFunc>
class awaitable_guard {
public:
    explicit awaitable_guard(CleanupFunc cleanup)
        : cleanup_(std::move(cleanup)), executor_(co_await asio::this_coro::executor) {}

    // 析构时触发清理(若未手动执行)
    ~awaitable_guard() {
        if (!completed_) {
            // 若未手动等待清理,启动detached协程确保执行(但推荐手动co_await)
            asio::co_spawn(executor_, cleanup_, [](std::exception_ptr) {});
        }
    }

    // 手动触发并等待清理完成
    asio::awaitable<void> execute() {
        if (!completed_) {
            completed_ = true;
            co_await cleanup_();
        }
    }

private:
    CleanupFunc cleanup_;
    asio::any_io_executor executor_;
    bool completed_ = false;
};

// 辅助函数创建守卫
template<typename CleanupFunc>
asio::awaitable<awaitable_guard<CleanupFunc>> make_awaitable_guard(CleanupFunc cleanup) {
    co_return awaitable_guard<CleanupFunc>(std::move(cleanup));
}

asio::awaitable<void> test_launcher() {
    co_await test_setup();
    auto guard = co_await make_awaitable_guard([]() { return test_cleanup(); });

    try {
        co_await test_body();
    } catch (...) {
        // 异常时先等待清理完成,再抛出
        co_await guard.execute();
        throw;
    }

    // 正常退出等待清理
    co_await guard.execute();
}

int main() {
    asio::io_context ioc;
    asio::co_spawn(
        ioc.get_executor(),
        test_launcher,
        [](std::exception_ptr ep) {
            if (ep) {
                try {
                    std::rethrow_exception(ep);
                } catch (int e) {
                    std::cout << "caught exception: " << e << std::endl;
                } catch (...) {
                    std::cout << "caught unknown exception" << std::endl;
                }
            }
        }
    );
    ioc.run();
}

这个方案适合需要在不同分支灵活控制清理时机的场景,同时保证清理逻辑的可靠性。

方案3:利用Boost.Asio的deferred链式组合

通过asio::deferred将操作组合,利用协程的异常传播特性确保清理执行:

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

namespace asio = boost::asio;

asio::awaitable<void> test_setup() {
    std::cout << "setup" << std::endl;
    co_return;
}

asio::awaitable<void> test_body() {
    std::cout << "body" << std::endl;
    throw 1;
    co_return;
}

asio::awaitable<void> test_cleanup() {
    std::cout << "cleanup begin" << std::endl;
    co_await asio::steady_timer(co_await asio::this_coro::executor, std::chrono::milliseconds(100)).async_wait(asio::use_awaitable);
    std::cout << "cleanup end" << std::endl;
    co_return;
}

asio::awaitable<void> test_chain() {
    co_await test_setup();
    
    // 用deferred包装清理操作,确保异常时也能执行
    auto cleanup_op = asio::deferred([]() { return test_cleanup(); });
    try {
        co_await test_body();
    } catch (...) {
        co_await cleanup_op;
        throw;
    }
    co_await cleanup_op;
}

int main() {
    asio::io_context ioc;
    asio::co_spawn(
        ioc.get_executor(),
        test_chain,
        [](std::exception_ptr ep) {
            if (ep) {
                try {
                    std::rethrow_exception(ep);
                } catch (int e) {
                    std::cout << "caught exception: " << e << std::endl;
                } catch (...) {
                    std::cout << "caught unknown exception" << std::endl;
                }
            }
        }
    );
    ioc.run();
}

这个方案利用Boost.Asio的异步操作组合特性,适合已有异步操作链的场景。


关键注意事项

  • 永远避免在清理逻辑中使用detached协程而不等待完成,否则可能导致io_context提前退出或资源泄漏
  • 异常需要正确传递:清理完成后重新抛出异常,确保co_spawn的回调能处理异常
  • 通用封装(如with_finally)能最大程度减少重复代码,提升代码可维护性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 08:22:02