PPL concurrency与co_await导致lambda捕获变量挂起时出作用域如何解决
解决方案
核心问题成因:PPL Concurrency运行时设计上不会持有传入
create_task的协程lambda对象所有权,顶层任务第一次co_await挂起后,栈上的lambda临时对象就会被销毁,所有捕获变量随之失效,即使你保存了返回的concurrency::task对象也无法避免。
以下是三种可落地的优化方案,都无需在挂起前手动复制所有捕获变量:
方案1:在协程内部持有lambda自身的强引用
这是改动最小的方案,通过shared_ptr包裹lambda,再在协程内部捕获这个智能指针,强制延长lambda生命周期到协程执行结束:
// 用shared_ptr包裹lambda,避免临时对象销毁 auto task_lambda = std::make_shared<std::function<concurrency::task<void>()>>( [self = self, this]() -> concurrency::task<void> { // 捕获自身的智能指针,只要协程还在运行,引用计数就不会归零 auto keep_alive = task_lambda; co_await 5s; MemberFunction(); co_return; }); concurrency::create_task(*task_lambda);
该方案不会产生循环内存泄漏:协程执行完毕后,局部变量keep_alive会随协程栈帧销毁,智能指针引用计数归零后会自动释放lambda对象。
方案2:封装通用顶层协程启动工具
如果需要频繁启动顶层协程,可以封装工具函数隐藏生命周期管理逻辑,业务层调用逻辑和原生create_task完全一致:
template<typename Func> void create_top_level_task(Func&& func) { auto wrapper = std::make_shared<std::decay_t<Func>>(std::forward<Func>(func)); concurrency::create_task([wrapper]() -> decltype(auto) { auto keep_alive = wrapper; return (*wrapper)(); }); } // 业务层直接调用即可 create_top_level_task([self = self, this]() -> concurrency::task<void> { co_await 5s; MemberFunction(); co_return; });
方案3:绑定生命周期到全局/类持有上下文
如果你的顶层任务需要和所属对象的生命周期绑定,可以在类中维护一个存活任务列表,任务执行完毕后自动移除,还支持析构时批量终止未完成任务:
// 类成员变量 std::mutex task_mtx; std::vector<std::shared_ptr<void>> running_top_tasks; // 启动任务时的逻辑 auto task_lambda = std::make_shared<std::function<concurrency::task<void>()>>( [self = self, this]() -> concurrency::task<void> { // 任务执行结束后自动从存活列表移除自身 auto _ = concurrency::create_scope_exit([this, lambda_ref = task_lambda]() { std::lock_guard lk(task_mtx); std::erase(running_top_tasks, lambda_ref); }); co_await 5s; MemberFunction(); co_return; }); { std::lock_guard lk(task_mtx); running_top_tasks.push_back(task_lambda); } concurrency::create_task(*task_lambda);
内容的提问来源于stack exchange,提问作者Joseph
相关产品推荐
相关产品推荐

