Skip to content

Wiki

future, async, promise and packaged_task

About 19 min read

Existing English translations were reused. 1 current source blocks remain in Simplified Chinese, without triggering paid translation.

std::thread Very suitable for expressing:

启动一个线程
↓
在线程里执行某个函数

But it itself does not directly solve:

线程函数怎么把返回值交回来?

线程里抛出的异常怎么传回来?

主线程怎么知道结果准备好了?

多个线程之间怎么建立“结果通道”?

C++11 in <future> provides a set of concurrency utilities that are more oriented toward "tasks and results":

std::future
std::async
std::promise
std::packaged_task
std::shared_future

Their core idea can be summarized in one sentence:

The result is not available yet, but it will be produced in the future; first obtain a future, and then wait for or retrieve the result later.

Therefore:

std::thread
→ 更关心“线程本身”

std::future / std::async / std::promise
→ 更关心“任务最终产生什么结果”

1.Future and async basics

1.1.std::future<T>What is it

std::future<T> It can be understood as:

A read end for a “future result”.

For example:

std::future<int>

Indicates:

未来某个时刻
会有一个 int 结果

If the task has no return value, you can use:

std::future<void>

1.2.Behind future, there is actually a shared state

future is itself neither a result nor a thread.

It is associated with one:

Shared state

It can be understood as:

生产者
写入结果 / 异常
      ↓
 shared state
      ↓
   future
读取结果 / 异常

Shared state may store:

任务返回值

任务抛出的异常

结果是否已经准备好

等待和同步所需的信息

Therefore:

future.get();

What is actually done can be understood as:

如果结果还没准备好
→ 等待

结果准备好以后
→ 取出结果

如果任务保存的是异常
→ 在这里重新抛出

1.3.The simplest std::async

Let's look at a simplest example:

#include <future>
#include <print>

int calculate(int a, int b)
{
    return a + b;
}

int main()
{
    std::future<int> result = std::async(
        std::launch::async,
        calculate,
        10,
        20);

    std::println("main continues");

    std::println("{}", result.get());
}

Possible output:

main continues
30

Here:

std::async(
    std::launch::async,
    calculate,
    10,
    20);

It can be understood as:

提交 calculate(10, 20) 这个任务
↓
让它异步执行
↓
返回一个 future<int>

This:

std::future<int> result

It is the reading end of future results.

Main thread continues execution:

std::println("main continues");

Until:

result.get();

If the task has already been completed:

直接取得结果

If the task is not yet complete:

当前线程在这里等待

Therefore:

std::async Responsible for "submitting tasks",std::future responsible for "retrieving results later".


1.4.asyncThe parameters of thread are very similar to those of

Basic form:

std::async(policy, callable, arg1, arg2, ...);

For example:

auto result = std::async(
    std::launch::async,
    calculate,
    10,
    20);

Among them:

policy
→ 任务怎么启动

callable
→ 要执行的函数 / lambda / 函数对象

arg1, arg2...
→ 传给这个可调用对象的参数

A callable object can be:

普通函数

lambda

函数对象

成员函数

If passing by reference is required, the same considerations apply:

std::ref(x)

as well as the lifetime issue of referenced objects.


2.async startup strategy

2.1.std::launch::async

std::asyncOne of the most commonly used startup strategies is:

std::launch::async

For example:

auto result = std::async(
    std::launch::async,
    task);

It means:

Require this task to be executed asynchronously.

It can first be intuitively understood as:

现在把任务安排出去执行

当前线程继续干自己的事

以后通过 future 等结果

Key point:

调用方关心的是任务和结果
而不是手动管理 thread 对象

So it and:

std::thread

The perspectives on usage are different.

std::thread More like:

我要创建并管理一个线程

std::async More like:

我要提交一个任务
最后拿它的结果

2.2.std::launch::deferred

Another startup strategy is:

std::launch::deferred

For example:

auto result = std::async(
    std::launch::deferred,
    task);

It does not immediately put the task into background execution.

It means:

First note this task down, and execute it later when someone is actually waiting for the result.

For example:

#include <future>
#include <print>
#include <thread>

int task()
{
    std::println(
        "task thread: {}",
        std::this_thread::get_id());

    return 42;
}

int main()
{
    std::println(
        "main thread: {}",
        std::this_thread::get_id());

    auto result = std::async(
        std::launch::deferred,
        task);

    std::println("before get");

    std::println("{}", result.get());
}

It can be understood as:

创建 async
↓
task 此时还没执行
↓
main 继续运行
↓
执行 result.get()
↓
就在调用 get() 的线程中执行 task()
↓
得到结果

So deferred is not:

后台线程晚一点执行

But closer to:

延迟到真正需要结果时
再由等待结果的线程执行

2.3.async and deferred difference

It can be simply remembered as:

std::launch::async

→ 任务异步执行
→ 当前线程可以继续
→ future 以后取结果

And:

std::launch::deferred

→ 任务先不执行
→ 第一次 get()/wait() 时才执行
→ 由调用等待函数的线程执行

Therefore:

async
≈ “现在就安排执行”

deferred
≈ “先记下来,用到结果时再执行”

2.4.What happens if no launch policy is written?

You can write it like this:

auto result = std::async(task);

However, in this case, the standard allows implementations to choose for themselves:

async

或者

deferred

So do not put:

std::async(task);

Automatically understood as:

一定立刻创建后台线程并发执行

If you explicitly require:

I just want the task to truly execute asynchronously.

Then it is best to write it explicitly:

std::launch::async

For example:

auto result = std::async(
    std::launch::async,
    task);

3.future waiting and exceptions

3.1.future::get()

get() is future one of the most important functions:

auto value = future.get();

It does two things:

1. 等待结果准备好

2. 取得结果

For example:

auto result = std::async(
    std::launch::async,
    [] {
        return 42;
    });

int value = result.get();

If the task has not yet finished executing:

get()
→ 等

After the task is completed:

get()
→ 返回 42

3.2.Normal future::get() can usually only be called once

For example:

auto result = std::async(
    std::launch::async,
    [] {
        return 42;
    });

int value = result.get();

Here:

result.get();

took the result out of this future.

Afterwards:

result.valid()

Usually becomes:

false

Therefore, ordinary std::future can be understood as:

A one-time pickup credential.

结果准备好
↓
get()
↓
结果被取走
↓
future 不再关联有效共享状态

Therefore, do not write it like this:

result.get();
result.get();

Second time get() will perform an invalid future operation, which usually throws:

std::future_error

3.3.future::valid()

You can use:

future.valid()

Query:

Whether this future is still associated with valid shared state.

For example:

auto result = std::async(
    std::launch::async,
    [] {
        return 42;
    });

std::println("{}", result.valid());

result.get();

std::println("{}", result.valid());

It can be understood as:

get() 前
→ future 还关联共享状态

get() 后
→ 普通 future 的结果已经被消费

Therefore:

valid()

No:

“任务成功了吗?”

Instead:

“这个 future 现在还有没有关联一个有效共享状态?”

3.4.future::wait()

If you only want to:

Wait for the task to finish, but do not retrieve the result for now.

Possible:

future.wait();

For example:

result.wait();

std::println("task finished");

int value = result.get();

Here:

wait()

Only responsible for waiting.

It will not take the result away.

So it is still possible to:

result.get();

It can be remembered as:

wait()
→ 等待,但不消费结果

get()
→ 等待 + 取走结果

3.5.wait() will not directly throw task exceptions

Assume that the asynchronous task throws an exception.

Call:

future.wait();

Just wait for the shared state to become ready.

It will not rethrow the task exception here.

Exceptions usually occur when:

future.get();

rethrow.

Therefore:

wait()
→ 确认任务已经结束

get()
→ 真正取得“结果或者异常”

3.6.future::wait_for()

You can wait for a period of time:

auto status = result.wait_for(
    std::chrono::milliseconds(100));

The return value may be:

std::future_status::ready
std::future_status::timeout
std::future_status::deferred

Respectively represent:

ready
→ 结果已经准备好了

timeout
→ 等待时间到了,但结果还没准备好

deferred
→ 这个任务使用 deferred 策略

Example:

#include <chrono>
#include <future>
#include <print>
#include <thread>

using namespace std::chrono_literals;

int main()
{
    auto result = std::async(
        std::launch::async,
        [] {
            std::this_thread::sleep_for(1s);
            return 42;
        });

    if (result.wait_for(100ms)
        == std::future_status::timeout)
    {
        std::println("not ready yet");
    }

    std::println("{}", result.get());
}

Output:

not ready yet
42

The whole process is:

任务开始
↓
main 最多等 100ms
↓
任务还没完成
↓
wait_for() 返回 timeout
↓
main 继续
↓
最后 get() 等待并取得结果

3.7.future_status::deferred

If:

wait_for(...)

Return:

std::future_status::deferred

Description:

This task is not executing slowly in the background at all.

It is just a deferred task.

For example:

auto result = std::async(
    std::launch::deferred,
    task);

At this point, keep writing:

result.wait_for(100ms);
result.wait_for(100ms);
result.wait_for(100ms);

it will not "just wait and complete in the background."

Because the logic of deferred is:

真正执行 get()/wait()
↓
调用这些函数的线程亲自执行任务

3.8.Exceptions in asynchronous tasks are propagated back through the future

This is future a very important ability.

For example:

#include <future>
#include <print>
#include <stdexcept>

int main()
{
    auto result = std::async(
        std::launch::async,
        []() -> int {
            throw std::runtime_error("task failed");
        });

    try
    {
        std::println("{}", result.get());
    }
    catch (const std::exception& e)
    {
        std::println("{}", e.what());
    }
}

Output:

task failed

Here, what the task thread throws:

std::runtime_error

It does not directly "jump" from a worker thread to the main thread.

The actual logic is:

异步任务抛异常
↓
异常被保存到 shared state
↓
main 调用 future.get()
↓
get() 发现共享状态里保存的是异常
↓
在 main 这里重新抛出

Therefore:

future.get();

What is obtained is not necessarily:

正常返回值

It could also be:

任务失败时保存的异常

This makes error handling for asynchronous tasks very similar to that of ordinary functions:

普通函数
→ 调用处 catch

异步任务
→ future.get() 处 catch

4.std::promise<T>

Previous:

std::async

A result channel will be created automatically.

But sometimes we want to control it ourselves:

什么时候写入结果

在哪个线程写入结果

写入正常值还是异常

In this case, you can use:

std::promise<T>

It can be understood as:

The "write end" of the result channel corresponding to future.

The relationship is:

promise<T>
   │
   │ set_value()
   │ set_exception()
   ↓
shared state
   ↓
future<T>
   │
   │ get()
   ↓
读取结果

So the simplest way to remember it is:

promise
→ 写

future
→ 读

4.1.promise::get_future()

Create:

std::promise<int> promise;

In the future, you can use:

std::future<int> future = promise.get_future();

Obtain the read end associated with this promise.

Therefore:

promise
和
future

connected through the same shared state.


4.2.The simplest example of promise

#include <future>
#include <print>
#include <thread>

void producer(std::promise<int> promise)
{
    promise.set_value(42);
}

int main()
{
    std::promise<int> promise;

    std::future<int> future =
        promise.get_future();

    std::thread thread(
        producer,
        std::move(promise));

    std::println("{}", future.get());

    thread.join();
}

Output:

42

The entire process can be understood as:

main 创建 promise
↓
main 从 promise 拿到 future
↓
main 把 promise 移动给 worker
↓
worker 调用 set_value(42)
↓
42 被写入 shared state
↓
main 的 future.get()
↓
读出 42

4.3.Why should Promise be std::move

std::promise It cannot be copied at will.

It is:

move-only

Therefore:

std::thread thread(
    producer,
    std::move(promise));

is moving ownership of the promise to the worker thread.

It can be understood as:

main
→ 保留 future,负责读

worker
→ 拿走 promise,负责写

Like so:

worker 写结果
↓
shared state
↓
main 读结果

it was established.


4.4.promise::set_value()

The production side can:

promise.set_value(42);

It means:

Write the final result to the shared state.

After writing:

future
→ ready

Waiting for:

future.get();

Execution can continue.

For:

std::promise<void>

No specific return value.

Possible:

promise.set_value();

Indicates:

The task has been successfully completed.


4.5.The result of a promise can usually only be set once.

For example:

promise.set_value(42);
promise.set_value(100);

is incorrect.

It can only be completed once for the same shared state.

First time:

set_value(42);

The result has already been determined.

Second time again:

set_value(...)

Typically throws:

std::future_error

So it can be understood as:

一个 promise
→ 最终只能提交一次最终结果

4.6.promise::set_exception()

promise can not only write normal values:

promise.set_value(result);

Exceptions can also be written:

promise.set_exception(
    std::current_exception());

Common notation:

void producer(std::promise<int> promise)
{
    try
    {
        int result = do_work();

        promise.set_value(result);
    }
    catch (...)
    {
        promise.set_exception(
            std::current_exception());
    }
}

Such consumer:

future.get();

Yes:

任务成功
→ 返回结果

任务失败
→ 重新抛出保存的异常

4.7.Complete promise exception example

#include <exception>
#include <future>
#include <print>
#include <stdexcept>
#include <thread>

void producer(std::promise<int> promise)
{
    try
    {
        throw std::runtime_error("failed");
    }
    catch (...)
    {
        promise.set_exception(
            std::current_exception());
    }
}

int main()
{
    std::promise<int> promise;

    auto future = promise.get_future();

    std::thread thread(
        producer,
        std::move(promise));

    try
    {
        std::println("{}", future.get());
    }
    catch (const std::exception& e)
    {
        std::println("{}", e.what());
    }

    thread.join();
}

Output:

failed

The process is:

worker 发生异常
↓
catch 捕获异常
↓
promise.set_exception(...)
↓
异常进入 shared state
↓
main 调用 future.get()
↓
异常在 main 中重新抛出

4.8.What is a broken promise

Assumption:

std::future<int> future;

{
    std::promise<int> promise;

    future = promise.get_future();
}

Here, when the promise leaves the scope:

没有 set_value()

也没有 set_exception()

But the future is still awaiting the outcome.

This situation is called:

broken promise

That is:

原本负责生产结果的人已经消失

但结果还没有产生

The standard library will not let a future wait forever in vain.

Afterwards:

future.get();

You will get a corresponding:

std::future_error

It can be understood as:

"As a result, the production end has already been cut off; there will be no more normal results."


4.9.All termination paths of a promise should have a definite result.

If using:

std::promise

The production side should handle this carefully:

成功
→ set_value()

失败
→ set_exception()

提前退出 / 取消
→ 也要设计清楚最终结果

Otherwise, it is easy to encounter:

broken promise

5.std::packaged_task

std::packaged_task solves another class of problems.

Assume the following already exists:

int add(int a, int b)
{
    return a + b;
}

Now I want to turn it into:

一个“执行以后自动把结果写到 future”的任务

Can be used:

std::packaged_task

It can be understood as:

Wrap a callable object into a task object that "carries a future result channel."


5.1.packaged_task Simplest example

#include <future>
#include <print>

int add(int a, int b)
{
    return a + b;
}

int main()
{
    std::packaged_task<int(int, int)> task(add);

    std::future<int> result =
        task.get_future();

    task(10, 20);

    std::println("{}", result.get());
}

Output:

30

Here:

std::packaged_task<int(int, int)>

Indicates:

这个任务:

接收两个 int
返回一个 int

Create:

std::packaged_task<int(int, int)> task(add);

Later:

task(10, 20);

Actually, it will:

调用 add(10, 20)
↓
得到返回值 30
↓
自动把 30 写入 shared state
↓
result.get() 取得 30

5.2.packaged_task and the biggest difference from promise

promise is:

You decide when to write the result.

For example:

promise.set_value(42);

And packaged_task is:

You give it a function, and when this function is executed, it automatically writes the function's return value to the result channel.

Therefore:

promise
→ 手动写结果

packaged_task
→ 执行函数,自动写结果

5.3.packaged_task automatically propagates the exception

Assume the wrapped function:

int work()
{
    throw std::runtime_error("failed");
}

Packaged as:

std::packaged_task<int()> task(work);

Execute:

task();

if work() throws an exception, packaged_task will save the exception to the shared state.

Afterwards:

future.get();

will rethrow this exception.

So it not only handles automatically:

返回值

It will also automatically handle:

异常

5.4.packaged_task in conjunction with thread

You can hand a packaged task to a thread for execution:

#include <future>
#include <print>
#include <thread>

int calculate()
{
    return 42;
}

int main()
{
    std::packaged_task<int()> task(calculate);

    std::future<int> result =
        task.get_future();

    std::thread thread(
        std::move(task));

    std::println("{}", result.get());

    thread.join();
}

Output:

42

Here:

std::thread thread(
    std::move(task));

Indicates:

把 task 移动给新线程
↓
新线程执行 task()
↓
task 内部调用 calculate()
↓
返回值写入 shared state
↓
result.get() 得到 42

5.5.packaged_task also move-only

Similar to promise:

std::packaged_task

也不能随意复制。

Typically required:

std::move(task)

Move it to:

线程

任务队列

worker

in.


5.6.Why packaged_task is well suited for task queues

Thread pools typically embody this idea:

用户提交函数
↓
包装成任务对象
↓
放进任务队列
↓
worker 线程取任务
↓
执行任务
↓
结果写进 shared state
↓
用户拿 future 等结果

This is exactly consistent with:

std::packaged_task

design fits very well.

Because the packaged task itself already contains:

怎么执行任务
+
结果写到哪里

are bound together.


5.7.promise and packaged_task How to choose

You can remember it like this:

5.7.1.promise

Suitable for:

我自己决定结果什么时候产生

结果不一定直接来自某个函数返回值

生产过程可能分很多步骤

For example:

promise.set_value(result);

Controlled by yourself.


5.7.2.packaged_task

Suitable for:

我已经有一个函数 / lambda

我就想把它包装成一个“执行后有 future 结果”的任务

For example:

std::packaged_task<int()> task(calculate);

Then:

task();

Automatically sends the return value into the future.

So the simplest distinction is:

promise
→ 手动生产结果

packaged_task
→ 包装可调用对象,自动生产结果

6.std::shared_future

Normal:

std::future<T>

More suitable:

一个消费者

Because:

future.get();

Usually it can only be consumed once.

If:

Multiple threads need to read the same asynchronous result

Can be used:

std::shared_future<T>

6.1.Convert future to shared_future

For example:

auto future = std::async(
    std::launch::async,
    [] {
        return 42;
    });

std::shared_future<int> shared =
    future.share();

Afterward, you can:

shared.get();
shared.get();
shared.get();

Multiple places can all read the same result.


6.2.shared_future more like "shared viewing"

Ordinary future:

get()
→ 把结果取走
→ future 失效

And shared future:

get()
→ 查看共享结果
→ 不会因为这次 get() 就把结果消费掉

Therefore:

future
→ 单消费者

shared_future
→ 多消费者

For example:

一次性加载配置
↓
得到 shared_future<Config>
↓
多个线程等待同一份配置
↓
全部读取同一个结果

7.Relationships and Choices

7.1.What exactly is the relationship between these few things?

The entire <future> system can be understood as:

                 shared state
                     │
       ┌─────────────┴─────────────┐
       │                           │
     写入端                      读取端
       │                           │
       │                        future
       │                    shared_future
       │
       ├─ promise
       │   → 手动写结果
       │
       ├─ packaged_task
       │   → 执行函数后自动写结果
       │
       └─ async
           → 提交任务并自动管理结果通道

Among them:

future
→ 负责以后读取结果

promise
→ 负责手动写结果

packaged_task
→ 把函数包装成“执行后自动写结果”的任务

async
→ 最方便的高级接口,直接提交任务并得到 future

shared_future
→ 允许多个消费者读取同一个结果

7.2.async and thread How to choose

If you are concerned about:

我需要一个长期存在的线程

线程里面一直循环

我要控制它什么时候停止

我要管理生命周期

More suitable:

std::thread
std::jthread

For example:

串口接收线程

传感器采集线程

机器人后台 worker

长期循环任务

If you are concerned about:

我有一个任务

它会算出一个结果

我以后要取得这个结果

任务中的异常也要传回来

More suitable:

std::async
std::future

For example:

并行计算一段结果

后台加载文件

异步计算某个值

7.3.async Not a thread pool

Do not treat:

std::async

Understand it as:

C++ 标准线程池

It is not a standard thread pool interface.

If you need:

固定数量 worker

不断提交大量小任务

任务队列

长期后台执行

可控停止

Usually, the following should be considered:

线程池

std::jthread

专门的任务系统

更完整的异步框架

Instead of constantly:

std::async(...)

8.Common Errors

8.1.Error: assumes that std::async() definitely creates a new thread

For example:

std::async(task);

If no policy is specified, the standard allows the choice of:

async
或
deferred

If true asynchrony is explicitly required:

std::async(
    std::launch::async,
    task);

8.2.Error: repeated for ordinary futureget()

Error:

future.get();
future.get();

General future results can usually only be consumed once.


8.3.Error: forgetting get() will rethrow the exception

For example:

auto result = std::async(
    std::launch::async,
    task);

result.get();

Here, not only may a return value be obtained, but an exception from the task may also be thrown.

Therefore, when necessary:

try
{
    result.get();
}
catch (...)
{
}

8.4.Error: promise was destroyed without writing a result

If promise:

既没有 set_value()

也没有 set_exception()

it disappears, and the future will get:

broken promise

8.5.Error: Treating promise / packaged_task as a copyable object

They are usually:

move-only

When transferring across threads, it is often necessary to:

std::move(...)

8.6.Error: use async for long-term background loops

For example:

std::async(
    std::launch::async,
    [] {
        while (true)
        {
            ...
        }
    });

If this task requires:

明确停止

长期运行

生命周期管理

Usually:

std::jthread

more semantically consistent.


9.Final summary

The most essential difference among this set of tools can be remembered as:

std::future
→ “以后我要读结果”

std::promise
→ “我手动把结果写进去”

std::packaged_task
→ “我执行这个函数,结果自动写进去”

std::async
→ “帮我提交任务,并直接给我 future”

std::shared_future
→ “多个地方都要读同一个结果”

Among them:

std::thread / std::jthread
→ 更偏线程生命周期

std::future / std::async
→ 更偏任务与结果

If you only remember one figure:

          生产结果
             ↓
 promise / packaged_task / async
             ↓
        shared state
             ↓
   future / shared_future
             ↓
          读取结果

This is the core logic of the entire <future> system.