C++ future、promise 与 async:接收后台结果,也接住后台异常
启动线程只是并发任务的一半,另一半是把结果或失败传回来。future 负责接收共享状态中的结果,promise 负责显式提供结果,async 则把任务启动与结果通道组合起来。
最低标准:C++14(示例使用初始化捕获)。示例显式使用 launch::async,不依赖默认启动策略。
1. 两种交付结果的方式
#include<exception>#include<future>#include<iostream>#include<stdexcept>#include<thread>#include<utility>intsquare(intx){if(x<0)throwstd::runtime_error("negative input");returnx*x;// 本例仅传入 -1;通用函数需额外处理乘法溢出}intmain(){autotask=std::async(std::launch::async,square,-1);try{std::cout<<task.get()<<'\n';}catch(conststd::exception&e){std::cout<<e.what()<<'\n';}std::promise<int>producer;autoresult=producer.get_future();std::threadworker([p=std::move(producer)]()mutable{try{p.set_value(42);}catch(...){p.set_exception(std::current_exception());}});worker.join();std::cout<<result.get()<<'\n';}future、promise 和 async 工具本身自 C++11 提供;本例使用 C++14 的初始化捕获来转移 promise。
输出为 negative input 和 42。async 任务的异常保存在共享状态里,在 get 时重新抛出;promise 也可以通过 set_exception 把工作线程的异常传回接收端。
2. get 与 wait 不是一回事
wait 等到共享状态就绪,但不取出结果,也不负责把任务异常重新抛出。get 等待并取得结果,或者抛出保存的异常。普通 future 的 get 会消耗关联状态,之后不能再重复 get。
需要多个接收者重复观察同一结果时,可以研究 shared_future;但它不自动让结果指向的可变对象线程安全。
3. 默认 async 不保证马上开线程
不指定策略时,实现可以选择异步执行或延迟执行。使用 launch::async 可以明确要求异步方式,但资源不足仍可能让任务启动失败,需要按实际场景处理异常。
来自 async 的 future 在特定条件下销毁时可能等待任务结束。因此把返回的 future 当成临时对象直接丢弃,可能让本以为并行的多次调用变成串行等待。应保存 future,并在合适的位置统一接收结果。
4. promise 是一次性交付通道
同一个共享状态不能随意重复 set_value 或 set_exception。若提供者在交付前销毁,接收端可能得到 broken_promise 错误。通道不是队列,不适合反复传递多条消息。
示例先 join 再 get,确保本地线程正常结束后才处理结果。实际代码若把可能抛异常的操作放在 join 之前,还需要用 RAII 管理线程,防止异常路径留下 joinable 的 thread。
异步不等于可以无限创建任务。任务很多时要限制并发量,并设计取消、超时和退出流程;这些能力不能仅靠一个 future 自动获得。