Concepts / Futures and promises

Futures and promises

A packaged_task simplifies setting up tasks connected with futures and promises so that they can be run on threads.

  • Programming

A Result That Must Travel Back

Suppose some computation has to run on a separate thread, and its answer has to come back to the code that started it. This is a common situation: the work happens in one place, and another part of the program needs the outcome. The question is how the answer gets from the thread that produced it to the code that is waiting for it.

What do you think happens?

A task is running on a worker thread and the main code needs its answer later. Which object should the main code hold so it can wait for and retrieve that answer?

  • The promise, because it is the object that holds the result

  • The future, because it is the handle used to retrieve the result

  • The task function itself, because it contains the computation

Reveal answer
Answer:

The future, because it is the handle used to retrieve the result.

The source describes two pieces that must reach the right places. The task that needs a result holds a future, which is the handle used to retrieve that result. The thread that produces the result holds the matching promise, which is the handle used to supply it.

Keep this question in mind as you read: when you have a task you want to run on a thread, how would you connect its outcome to a future that someone else can wait on? The rest of this article follows the path that packaged_task creates for that connection.

Why the Two Handles Must Match

A future and a promise are a matched pair. The promise is the write side: the producing thread uses it to supply the result. The future is the read side: the consuming code uses it to wait for and retrieve that same result. Both handles refer to one result, so getting them to the correct parties, without mixing them up, is the setup work that needs to be done before the task can usefully run on a thread.

Because the two handles are connected, the code that holds the future does not need to know how the result was produced or where it was computed. It only needs to wait for the answer and retrieve it. The code that holds the promise is the one responsible for supplying the answer.

What packaged_task Takes Over

A packaged_task acts as a wrapper. You give it a task, and it supplies the surrounding code that takes whatever the task produces and places it into a promise. The source describes this as wrapper code that puts the return value or the exception from the task into its promise. The programmer does not have to hand-write the step that moves a finished result, or a thrown exception, into the promise. The packaged_task performs that step as part of running the task.

ConcernWiring a promise and a function by handUsing a packaged_task
Connecting the task to a promiseThe programmer must write the step that moves the result into the promiseThe packaged_task performs that step as part of running the task
Handling a normal returnThe programmer must explicitly place the return value into the promiseThe wrapper places the return value into its promise
Handling a thrown exceptionThe programmer must explicitly place the exception into the promiseThe wrapper places the exception into its promise
Giving the caller a handleThe programmer must make sure the matching future reaches the consumerCalling get_future on the packaged_task returns the matching future

Manual wiring versus packaged_task, based on the responsibilities the source assigns to the wrapper.

Notice that there are two possible outcomes the wrapper has to handle. A task can finish and produce a return value, or it can end by raising an exception. The packaged_task carries either outcome into the promise, so that whoever later reads the result through the future receives the same kind of answer the task itself produced.

How the Pieces Are Linked

The packaged_task holds the task, and it holds the promise. From the packaged_task, the caller can obtain the matching future. The thread runs the packaged_task, and that run is what eventually fills the promise. The diagram below shows which object holds a reference to which, so you can see where each piece sits.

Task functionthe work to runpackaged_taskwrapper around the taskPromisewrite side of the resultCaller codewaits on the futureFutureread side of the resultWorker threadruns the packaged_taskwrapsholdsget_future returnsrunshandle given to
Which objects hold references to each other, and how the packaged_task connects the function, the promise, and the future that the caller gets?

The relationship is one of pairing. The packaged_task holds the promise, and get_future hands out the matching future. The future is the read side and the promise is the write side of the same result. The producer side and the consumer side never touch the same object directly: the task produces its outcome, the wrapper moves that outcome into the promise, and the future is the only handle the consumer uses.

Value Path and Exception Path

When the task finishes on the worker thread, the wrapper checks which of two things happened. If the task returns normally, the return value goes into the promise. If the task ends by raising an exception, the exception goes into the promise instead. In both cases the promise is filled, so the future has something to deliver to the caller.

Task runs on threadTask finishesnormally?Return value intopromiseFuture ready forcallerretrieves valueException intopromiseFuture ready forcallerretrieval reports exceptionyes: returnsno: throws
Given a task that either returns normally or throws, which branch sets the value in the promise and which branch sets the exception, and what does the caller see in each case?

Getting the Future with get_future

A packaged_task does not only run the work. It also gives you the other half of the connection. If you call its get_future operation, the packaged_task returns the future that corresponds to its promise. This is how the consuming side of the program receives a handle to the eventual result without touching the promise directly.

Callerpackaged_taskWorker threadPromiseFuture1. get_future()2. returns future3. moves task to thread4. runs task5. places value or exception6. waits and retrieves
In what order do the caller, the packaged_task, the promise, and the future interact when get_future is called before the task is moved to a thread?

Read the sequence from top to bottom. The caller first asks the packaged_task for its future. The future is the handle the caller keeps. The task is then moved to a thread, which runs it. When the task finishes, the packaged_task fills the promise, and the caller can use the future it already holds to wait for and retrieve the answer.

Splitting a Sum Across Two Tasks

Worked example

Two tasks summing halves of a vector of doubles

The source illustrates the idea by splitting a vector<double> sum into two tasks. Each task uses the standard-library accumulate() on half of the elements. Trace how each task is connected to its own future.

  1. 1
    Divide the work

    The elements of the vector are split so that one task covers the first half and the other task covers the second half. Each task uses accumulate() on its half.

  2. 2
    Wrap each task

    Each half-sum task is given to its own packaged_task. The wrapper will place that task's outcome into the promise belonging to that packaged_task.

  3. 3
    Obtain each future

    The caller calls get_future on each packaged_task. Each call returns the future corresponding to that packaged_task's promise, so the caller now holds one handle per half.

  4. 4
    Run on threads

    Each packaged_task is run on a thread. The programmer does not hand-wire a promise to each thread; the packaged_task and its future take care of that connection.

  5. 5
    Retrieve results

    When a thread finishes its task, its promise is filled with the return value or exception. The caller reads each result through the future that matches it.

Result

Each half of the computation has its own packaged_task, promise, and future. The caller reads each outcome through the future that corresponds to it, and never touches a promise directly.

Mistakes Learners Make with the Pairing

  • Common mistake

    Thinking the caller should hold the promise

    Why it fails

    The source assigns the promise to the producing side. The future is the handle used to retrieve the result, so the consuming code holds the future.

    Do this instead

    Give the future to the code that needs the result, and keep the promise on the side that supplies it.

  • Common mistake

    Assuming an exception bypasses the promise

    Why it fails

    The wrapper carries either a return value or an exception into the promise. An exception is not a separate route that skips the future.

    Do this instead

    Expect the consumer to receive the exception through the same future that would have carried the value.

  • Common mistake

    Writing the step that moves the result into the promise by hand

    Why it fails

    The packaged_task is designed to perform that step as part of running the task, so manual placement duplicates work the wrapper already does.

    Do this instead

    Let the packaged_task place the outcome into its promise and use get_future to obtain the matching future.

  • Common mistake

    Assuming the thread and the promise must be wired together manually, when the packaged_task and its future take care of that connection

Checking Your Understanding

MEDIUM

A packaged_task wraps a task that will be run on a worker thread. Explain, in your own words, which object the caller uses to get the result, which object the wrapper fills, and what happens if the task throws an exception instead of returning a value.

Hints
  • Recall that the future is the read side and the promise is the write side of the same result.
  • Think about which of the two outcomes the wrapper handles: a return value or an exception.
  • Trace the order: get_future comes first, the task runs on a thread, then the promise is filled.

Key Takeaways

  • A packaged_task simplifies setting up tasks connected with futures and promises so that they can be run on threads.
  • The packaged_task acts as a wrapper that places the task's return value or exception into its promise, so the programmer does not hand-write that step.
  • The promise is the write side and the future is the read side of the same result; the caller holds the future and never touches the promise directly.
  • Calling get_future on a packaged_task returns the future that corresponds to its promise, giving the consumer a handle to the eventual result.

More concepts starting with F