Difference between promise, packaged task and async
We know three ways of associating a task with a
std::future
. They are std::async
,
std::packaged_task
and std::promise
.
The benefit of the packaged task over std::async
is to
decouple the creation of the future with the execution of the task.
The benefit of the promise over std::async
and
std::packaged_task
is that the promise can communicate a
value to the future at a different time than at the end of a function call. Let
me explain what I mean with this.
Differences
std::async
The std::async
takes a function as an argument and returns a
future. The future has the same template parameter as the return type of the
function. When the function returns, its return value sets the
future. Therefore, the std::async
communicates the value at
the end of the function call. For example:
The std::async
communicates the value to the future at the end of
function
execution.
std::packaged_task
The constructor of the std::packaged_task
takes a function as
an argument and creates a packaged task object. The template parameter of the
packaged task is the signature of the function.
The .get_future()
member function of the object creates a
future. The future has the same template parameter as the return type of the
function
The execution of a packaged task, which forwards arguments to the function
and runs it, sets the return value of the function to the future. Therefore
again, the std::packaged_task
communicates the value at the
end of the function call. For example:
task(2.5)
executes function(2.5)
and sets
the return value of the latter call to the future. The
std::packaged_task
communicates the value to the future at
the end of function
execution.
std::promise
There are no such restrictions with std::promise
. The promise
can explicitly set a value to a future anytime and not only at the end of a
function call. Let’s look at an example of such behavior.
Manager and mechanic
Imagine a car repair shop. A customer would like to buy a car. She would like to be convinced that the engine of the car is OK. The manager of the shop instructs the mechanic to inspect the engine. As soon as it is clear that the engine is OK, the manager can negotiate a contract with the customer.
Let’s model this story with a program. We have a class which represents a car.
The Car
has four data members: hood
,
electricity
, exhaust
and
engine
. They are all represented by a boolean variable. If a
data member is true
than everything is OK, otherwise
something is wrong (the part is broken or missing).
Additionally, there are data functions which can remove/add certain part of the
car. The .is_engine_ok()
tells us whether the engine is OK.
The next part describes the behavior of the mechanic.
The mechanic first removes the hood, electricity and the exhaust system in order to get an access to the engine. Then he communicates the status of the engine to the manager via the promise. If the engine is OK, the mechanic puts the car back together.
The point is that the promise communicates the status of the
engine in the middle of the function. This is not possible with
std::async
or std::packaged_task
, because
they communicate a value only at the end of the function call.
The last part describes the manager.
The manager would like to know if the car engine is OK. Therefore, he instructs the mechanic to check the engine. This is done by creating a new thread which represents the mechanic:
The function mechanic(...)
requires two arguments: a car and a
promise. The std::ref
correctly passes the
car
as a reference to the mechanic(...)
. Without
std::ref
a copy of the car
would be
passed. Look at Passing arguments to a thread
for a detailed explanation.
The promise
is not copyable, therefore
std::move
moves it to the mechanic(...)
.
After creating and detaching the thread, the manager waits for the answer from
the mechanic. If the answer is true
– the car engine is OK
– then the manager negotiates the contract for selling the car. Otherwise, he
buys a new engine.
If you are not familiar with std::promise
, look at
Promise.
Running the example
The output of the example is:
The figure below describes the timeline of the program.
This example shows the difference between the promise and other synchronization mechanisms. The promise communicates the status of the engine in the middle of the function and not at the end.
Summary
The difference between the promise and other synchronization mechanisms is that the promise can communicate the value at a different time than at the end of the function call. We illustrated this difference with the example.