异步机制

异步把“提交工作”和“取得结果”分开:调用者可以先推进其他工作,稍后再等待结果。它并不保证一定创建线程,也不能替代取消、限流和任务生命周期设计。

核心对象与关系

1
2
3
4
产生结果的一端                         使用结果的一端
std::promise<T> ── set_value/exception ──> std::future<T>
std::packaged_task ── 调用可调用对象 ────> std::future<T>
std::async ─────── 启动/延迟任务 ────────> std::future<T>

一个 std::future<T> 通常只有一个消费者:get() 只能调用一次。需要让多个消费者读取同一结果时,将 future 转换为 std::shared_future<T>。

类型 何时使用 结果与异常
std::async 简单的一次性计算 自动建立 future 共享状态
std::promise<T> 一个执行路径手动向另一路径报告结果 set_value / set_exception
std::packaged_task 将可调用对象和 future 分离,常用于任务队列 调用 task 时填充结果
std::future<T> 单次取得或等待结果 get() 会重新抛出任务异常

std::async 与启动策略

函数原型(概念形式):

1
2
3
template<class F, class... Args>
std::future<std::invoke_result_t<std::decay_t<F>, std::decay_t<Args>...>>
std::async(std::launch policy, F&& function, Args&&... args);

function 和 args 是任务及其参数;返回的 future 关联任务的结果。参数会被保存,引用语义需显式 std::ref,并仍需保证生命周期。

1
2
3
4
5
6
7
8
9
#include <future>
#include <iostream>

int add(int left, int right) { return left + right; }

int main() {
auto result = std::async(std::launch::async, add, 3, 4);
std::cout << result.get() << '\n'; // 7;同时等待完成
}
策略 任务何时执行 注意
std::launch::async 由实现异步执行,通常使用新线程 不应假设它是可复用线程池
std::launch::deferred 第一次 get() 或 wait() 时,在调用这些函数的线程执行 wait_for 可返回 deferred
未指定策略 实现可选择上述任一方式 若确实需要异步,显式指定 async

不要丢弃 std::async(std::launch::async, ...) 返回的临时 future:关联异步任务的 future 在临时对象析构时可能等待任务完成,表面上的“并发调用”会退化为串行。应保存 future,并在明确位置 get() 或 wait()。

等待、超时与异常

成员函数 结果 注意
get() 等待、取走值,并重新抛出异常 仅一次;之后 valid() 为 false
wait() 只等待完成 不取走值,之后仍可 get()
wait_for() / wait_until() 返回 ready、timeout 或 deferred 超时不取消任务
valid() 是否仍关联共享状态 对已 get 或移动后的 future 为 false
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <chrono>
#include <future>
#include <iostream>
#include <thread>

int slow_task() {
std::this_thread::sleep_for(std::chrono::milliseconds(100));
return 42;
}

int main() {
auto result = std::async(std::launch::async, slow_task);
if (result.wait_for(std::chrono::milliseconds(10)) == std::future_status::timeout) {
std::cout << "任务尚未完成;它仍在运行\n";
}
std::cout << "结果: " << result.get() << '\n';
}

promise:手动交付值或异常

promise 和它产生的 future 共享一个结果槽。每个 promise 只能 get_future() 一次,通常只能成功设置一次值或异常;违反约束会抛出 std::future_error。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <exception>
#include <future>
#include <iostream>
#include <stdexcept>
#include <thread>

void worker(std::promise<int> result) {
try {
// 真实程序中也应捕获任务内部可能抛出的异常。
result.set_value(10 + 20);
} catch (...) {
result.set_exception(std::current_exception());
}
}

int main() {
std::promise<int> promise;
auto result = promise.get_future();
std::thread thread(worker, std::move(promise)); // promise 不可复制,只可移动
try {
std::cout << "结果: " << result.get() << '\n';
} catch (const std::exception& error) {
std::cerr << "任务失败: " << error.what() << '\n';
}
thread.join();
}

若 promise 在未设置结果时被销毁,future 的 get() 会抛出 std::future_error(broken promise)。这通常意味着异常或取消路径漏掉了交付结果。

packaged_task 与 shared_future

packaged_task<R(Args...)> 包装可调用对象;调用该 task 时,返回值或异常会自动写入其 future。它适合由队列的消费者执行任务、由提交者保留 future 的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <future>
#include <iostream>
#include <thread>

int square(int value) { return value * value; }

int main() {
std::packaged_task<int(int)> task(square);
auto result = task.get_future();
std::thread worker(std::move(task), 6);
std::cout << result.get() << '\n'; // 36
worker.join();
}

多个读取者需要同一份结果时:

1
2
3
std::future<int> one_reader = std::async(std::launch::async, [] { return 123; });
std::shared_future<int> many_readers = one_reader.share();
// many_readers 可复制;每个副本均可重复调用 get(),得到 const int&。

选择与边界

  • 只需一个简单计算结果:std::async(std::launch::async, ...) 可读性高。
  • 线程已由你管理、需要手动报告结果或异常:promise + future。
  • 要构建有界任务队列:用 packaged_task 表达任务;队列的同步、停止和背压仍需自行设计,见同步机制。
  • future 没有标准的通用取消 API。超时只说明当前未完成,不能安全终止任务;应设计协作式停止协议(C++20 可用 stop_token)。

常见误区与实践建议

  • std::async 不是线程池;对大量短任务反复调用可能产生明显调度开销,需基准测试。
  • get() 既是同步点也是异常传播点,调用处必须准备处理异常。
  • 不要让 UI/事件循环线程在不合适的位置阻塞 get();可以先检查、回调或把等待放到合适的工作路径。
  • 若任务访问外部对象,future 的存在不延长那些对象的生命周期;用值捕获、共享所有权或明确 join 保证安全。

编译命令:c++ -std=c++17 -Wall -Wextra -Wpedantic -pthread file.cpp。