跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

C++实现异步任务的进度实时通知 _ future/promise与原子变量配合【源码】

std::future 无法获取进度是因为其设计仅支持一次性获取最终结果,缺乏进度回调机制;应改用 std::promise 封装结果、std::atomic_int 安全共享进度值。 为什么不能只用
std::future
获取进度?
std::future
的设计目标是「一次性获取最终结果」,它没有内置的进度回调机制,
wait_for
或
wait_until
只能轮询是否就绪,无法得知 30%、70% 这类中间状态。强行在循环里反复调用
get()
会阻塞或抛异常(已移动后调用),根本不可行。 真正需要的是:主线程能非阻塞读取当前进度值,工作线程能安全更新——这正是原子变量(
std::atomic_int
等)的典型场景。 用
std::promise
+
std::atomic_int
搭配的最小可行结构 核心思路:把进度暴露为只读原子变量,把完成结果封装进
std::promise
。两者解耦,互不干扰。
std::promise
负责传递最终返回值(比如计算结果、错误码)
std::atomic_int progress{0}
负责被多线程读写,初始值 0,范围建议 0–100(百分比)或 0–total(任务项数) 工作线程更新时用
progress.store(new_val, std::memory_order_relaxed)
(无需强同步) 主线程读取时用
progress.load(std::memory_order_relaxed)
,避免锁开销 示例片段: 立即学习 “ C++免费学习笔记(深入) ”; C函数速查手册(CHM版) C函数速查手册(CHM版) 下载
std::promise result_promise; std::atomic_int progress{0}; std::thread worker([&] { for (int i = 0; i <= 100; ++i) { // 模拟耗时工作 std::this_thread::sleep_for(std::chrono::milliseconds(50)); progress.store(i, std::memory_order_relaxed); } result_promise.set_value(42); // 任务完成 }); // 主线程:实时读取进度 while (progress.load(std::memory_order_relaxed) < 100) { std::cout << "Progress: " << progress.load() << "%\n"; std::this_thread::sleep_for(std::chrono::milliseconds(200)); } auto result = result_promise.get_future().get(); // 阻塞取结果
进度通知的常见陷阱与绕过方式 直接在工作线程里调用回调函数(比如 lambda)看似直观,但极易引发竞态:主线程可能正在销毁回调对象,而工作线程还在调用它。原子变量天然规避了这个问题——它不持有任何对象生命周期依赖。 不要用
std::function
或裸指针传回调:销毁时机难控制,且每次调用都要加锁或原子操作保护 避免用
std::mutex
保护普通
int
进度变量:锁竞争会拖慢高频更新(如每毫秒更新一次) 别把
progress
设为
std::atomic
:不是所有平台都提供无锁实现,可能退化为内部锁;用
int
表示比例再除算更稳妥 如果需要更丰富的状态(如“暂停中”“出错”),可用
std::atomic
编码状态机,例如 -1=error, 0=pending, 1=running, 2=paused, 100=done 何时该换用
std::jthread
和取消机制? 当任务支持中途取消(比如用户点了“停止”),仅靠原子变量不够。此时需配合可协作中断:
std::jthread
(C++20)自带
std::stop_token
,比手搓
std::atomic_bool cancel_requested
更规范。
std::jthread
析构时自动请求停止,避免资源泄漏 工作函数内定期检查
token.stop_requested()
,及时退出循环 仍可复用同一个
std::atomic_int progress
,取消不影响进度读取逻辑 注意:取消 ≠ 中断线程,只是设置标志位,工作线程必须主动响应 进度通知本身不解决取消问题,但它和取消机制可以正交组合——这是 异步任务 健壮性的关键分层。 真正容易被忽略的是:进度值的语义一致性。比如工作线程更新到 80 后崩溃,主线程读到 80 会误判为“即将完成”。没有银弹,只能靠日志、超时、或完成态双重校验(如额外一个
std::atomic_bool finished
)来补足。

相关文章