76 / 163 · C++11 · 11 min
Task results: async, future, promise, and launch policy
A future is the receiving end of a one-shot result; it is not a background thread. The launch policy of async chooses independent execution or deferred evaluation. A promise is how a value or exception is submitted by hand. Handle the result, and also be explicit about waiting, exception propagation, and when the associated task ends.
In this lesson
Choose the execution policy before talking about concurrency
std::async returns a future associated with shared state. Explicit launch::async means the function runs with the semantics of an independent thread of execution. launch::deferred stores the function until the first untimed wait or get, and then the waiting thread runs it. The default policy lets the implementation choose. Do not treat default async as an interface that guarantees background execution.
The example launches the computation of forty-two with async and the task that sets ran with deferred. Reading ran has no concurrent conflict: the deferred task has not run yet, and get is called by the main thread. If the task is discarded and deferred evaluation is never triggered, it may never run at all. Choose the policy by whether the side effects are required, not only by whether the return value is convenient.
A future manages the result and the exception
wait() only waits until the shared state is ready; it does not take the result. get() waits and takes the value, or rethrows the exception stored by the task. get on an ordinary future may be consumed only once. After that the future is no longer associated with that shared state. When several receivers are needed, convert to shared_future; each receiver holds its own copy. Mutable objects inside the result still need synchronization.
async stores an exception from the work function in the result channel. It does not let the exception escape the thread entry. The caller should catch around get. A wait that does not throw does not mean the computation succeeded. Another easy miss: if an async task started with launch::async has not finished, releasing the last reference to the associated shared state may wait for the task. Dropping a temporary future can turn consecutive calls that were meant to run in parallel into a serial sequence.
A promise is the submitting end, not a launcher
A promise and a future share one result slot. The former calls set_value or set_exception; the latter waits to receive. A promise does not create a thread by itself. Submitting a result normally resumes the waiter and establishes the needed synchronization. Result readiness does not mean a hand-created producer thread has already finished. The thread lifetime still has to be managed.
The example catches an exception in a thread and gives current_exception to the promise. The main thread joins first, then gets and catches the original exception type. Each shared state can be completed only once. A second submission reports an error. If a promise is destroyed before it is completed, the receiver gets broken_promise instead of waiting forever. A real interface should agree how a value, a failure, and a cancellation are expressed. An unset result is not a normal cancellation.
Pitfalls
- Do not assume every
futuredestructor waits for a thread. Shared state created by an ordinarypromisedoes not provide that thread-reaping guarantee; a hand-created thread still needsjoin. - Do not
get,wait, or destroy a possibly waitingasyncfuturewhile holding a lock the asynchronous task needs, or the wait for the result becomes a deadlock.
Run an example
Minimum C++11 · complete program · Download .cpp
#include <cassert>
#include <exception>
#include <future>
#include <iostream>
#include <stdexcept>
#include <thread>
int main() {
auto answer = std::async(std::launch::async, [] { return 6 * 7; });
bool ran = false;
auto deferred = std::async(std::launch::deferred, [&] {
ran = true;
return 7;
});
assert(!ran);
const int delayed = deferred.get();
assert(ran && delayed == 7);
assert(!deferred.valid());
const int value = answer.get();
assert(value == 42);
std::promise<int> promise;
auto failure = promise.get_future();
std::thread worker([&] {
try {
throw std::invalid_argument("negative input");
} catch (...) {
promise.set_exception(std::current_exception());
}
});
worker.join();
bool caught = false;
try {
(void)failure.get();
} catch (const std::invalid_argument&) {
caught = true;
}
assert(caught);
std::cout << value << ' ' << delayed << ' ' << caught << '\n';
}
Compile locally
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread concurrency-async.cpp -o example && ./exampleExpected result
42 7 1
CHECK YOUR UNDERSTANDING
Close the answer. Explain it.
Change the example's promise producer thread so it submits the integer 9 normally. How should the receiving side change? Can get be called twice to obtain the same value?
Show a reference answer
The worker should call promise.set_value(9). The main thread still worker.join()s first, then uses const int result = failure.get() and asserts result == 9, deleting the exception-catching branch. This ordinary future cannot be get again; the first call already consumed the shared state. If repeated reads are needed, call share() before the first consumption to obtain a shared_future, and read through its get.
Check the sources
- cppreference: std::async launch policy, exceptions, and destructor waiting
- cppreference: std::promise shared state and broken_promise
Drafts and official chapters change. The version mark is only the example’s minimum.