c++怎么使用std::async执行异步任务_c++ 未来对象std::future结果获取【教程】

std::async默认策略不保证真异步,需显式指定std::launch::async;std::future::get()仅能调用一次且阻塞,未调用则析构时隐式wait;不支持多消费者,须用std::shared_future或std::move转移所有权;异常在get()时重抛。

std::async 启动异步任务时,launch策略决定是否真异步

默认情况下 std::async 不一定真正并发执行——它可能延迟到 get()wait() 时才同步运行(std::launch::deferred 策略)。这常导致“以为异步,实则卡主线程”的问题。

  • 显式指定 std::launch::async 强制新开线程:
    auto fut = std::async(std::launch::async, []{ return 42; });
  • 不指定策略时,编译器可自由选择;MSVC 默认倾向 deferred,GCC/Clang 多数情况走 async,但不可依赖
  • 混合使用两种策略时,注意资源竞争:多个 std::launch::async 任务共享线程池(标准未规定池大小,实际由实现决定)

std::future::get() 只能调用一次,且会阻塞等待完成

get() 是获取结果的唯一合法方式,但它有强副作用:首次调用后,该 std::future 对象进入无效状态,再次调用会抛出 std::future_error(错误码为 std::future_errc::no_state)。

  • 调用前可用 valid() 检查是否还持有有效状态:
    if (fut.valid()) { auto res = fut.get(); }
  • 若只需等待完成、不关心返回值,用 wait() 更安全
  • 在析构前未调用 get()wait(),会导致析构时隐式调用 wait() —— 这可能让你在意外位置被阻塞

std::future 不支持多消费者,也不能跨线程转移所有权

std::future 是独占所有权类型,移动后原对象失效,且不能拷贝。这意味着它无法像 std::shared_future 那样被多个线程同时等待或取值。

  • 需要共享结果时,改用 std::shared_future
    auto fut = std::async([]{ return "done"; }); auto sf = fut.share();
  • 想把 future 传给另一个线程处理?必须用 std::move
    std::thread t([f = std::move(fut)]{ f.get(); });
  • lambda 捕获 future 时也需显式移动:
    [fut = std::move(fut)]() mutable { fut.get(); }

异常传播:异步函数抛异常,get() 会重新抛出

如果 async 中的函数抛出异常,std::future::get() 不会返回值,而是直接重抛该异常(类型不变)。这是 std::future 的核心设计之一,但容易忽略其传播时机

  • 异常只在第一次 get() 时抛出;后续调用仍报 no_state
  • 若异步任务已抛异常但你只调用了 wait(),异常不会触发,直到你调用 get()
  • 建议始终用 try/catch 包裹 get(),尤其在封装异步调用时:
    try { auto res = fut.get(); } catch (const std::exception& e) { /* 处理 */ }

真正麻烦的是:一旦 std::future 被 move 出作用域,又没在析构前 get()wait(),程序就可能卡死在析构点——这个行为静默且难以调试。