Futures and get() in Concurrent Tasks
Put and get are two halves of one channel. Operations on the promise side correspond to the operations on the future side, and that correspondence is why the two must be matched.
A Result That Arrives Later
In concurrent programs, one part of the program often starts work and another part needs the outcome of that work. The second part cannot always wait for the work to finish in the same place where it was started. It needs a way to ask for the result and a way for the result to be delivered when it is ready. This article looks at the pair of tools built for that job: the future, which is where a result will appear later, and the promise, which is how the result gets put there.
Keep one question in mind as you read: when a task finishes, who is responsible for putting the outcome somewhere, and who is responsible for taking it out? The answer is the core of this topic, and the sections below trace that handoff one step at a time.
Reader and Writer Halves
A promise is the sending side that matches a future's get(), providing the put operations set_value() and set_exception(). A future is the receiving side: it offers get() to retrieve a result that will appear later.
A future is a place where a result will appear later. Someone has to put that result there, and someone else reads it out. The future is the reading side and offers get() to retrieve the result. The promise is the writing side, and its main job is to provide simple put operations that match the future's get(). The two halves are designed to fit together, so a result produced in one part of a program can be delivered to the future that another part is waiting on.
The names future and promise are historical. They do not describe the mechanics perfectly, so it helps to think of them as a pair of handles. The promise is the handle you use to send something in. The future is the handle you use to take it out. Whatever goes in through the promise is what comes out through the future.
Put and get are two halves of one channel. Operations on the promise side correspond to the operations on the future side, and that correspondence is why the two must be matched.
Following One Result Through the Handoff
The diagram below shows the order of events. The producing task performs a put on its promise. The promise stores the outcome in the future. Later, a separate reader calls get() on the future to take the outcome out. Read it as a relay: the put comes from the sending side, and the get comes from the receiving side.
Notice that the consumer's get() is the step that finally exposes the outcome. The consumer does not need to know how the producer did its work. It only needs the future, and the future only gets filled when the promise performs one of its put operations. The same order holds whether the put carried a value or an exception.
Choosing a Value or an Exception
Suppose you hold a promise and need to send a result of type X to the future. You have exactly two options. You can pass a value, which is a normal result of type X. Or you can pass an exception, which reports that the work failed. The promise's operation for the first option is set_value(), and the operation for the second is set_exception(). Whichever you choose, the future receives it, and a later get() call is where the outcome becomes visible.
When sending a result of type X to a future, you pass either a value or an exception, never both as a single outcome.
The value path is the ordinary case. The task finishes its work successfully and has a real result to give. It calls set_value() on the promise with that result, and the future now holds it.
Sometimes the work does not succeed. Instead of a value, the promise must carry an exception to the future, and the operation for that case is set_exception(). The purpose is the same as the value path, with a different payload. The future is still the place where the outcome is read, but the reader learns that something went wrong rather than receiving a normal result.
Capturing a Failure with current_exception()
The source pack names a helper for the failure case: current_exception(). It refers to the exception that was caught. When a task has caught an error, current_exception() gives a way to point at that caught exception so that it can be passed along to the future. In other words, it is the bridge between the handling code and the promise.
Worked Walkthrough: A Successful Task
Delivering a successful result through a promise
A producing task computes one result of type X. A consumer elsewhere in the program needs that result and calls get() on the matching future. Trace what happens on the success path.
- 1Create the pair
The promise is the sending side and the future is the receiving side. They are matched, so what goes in through the promise comes out through the future.
- 2Finish the work
The producing task completes successfully, so it has a real result of type X and not an error.
- 3Put the value
The task calls
set_value()on the promise with that result. This is the value path, the ordinary case. - 4Hold the outcome
The future now holds the value. The producer's job on this side is finished.
- 5Read the outcome
The consumer calls
get()on the future. The outcome becomes visible, and the consumer receives the value.
The consumer receives the value that the producer put in with set_value(). The consumer never needed to know where or how the work was done.
This walkthrough is an original illustration built from the handoff described above. It shows the value path only. The failure path follows the same order, except that the producer calls set_exception() with a caught exception, and the consumer's get() reports that the work failed.
Mistakes Beginners Make with the Two Halves
Common mistake Treating the promise and the future as the same object
Why it failsThey are the two halves of a pair. The promise is the writing side with put operations, and the future is the reading side with
get().Do this insteadKeep the roles separate. Put operations belong to the promise, and get() belongs to the future.
Common mistake Thinking a single outcome can carry both a value and an exception
Why it failsThe source pack states that you pass either a value or an exception, never both as a single outcome.
Do this insteadChoose one: set_value() for a normal result or set_exception() for a failure.
Common mistake Calling current_exception() outside the code that caught the error
Why it failscurrent_exception()refers to the exception that was caught, so it has meaning only in the handling code that caught it.Do this insteadCall it inside the catch handling, then pass the result to the promise's failure path.
Common mistake Expecting get() to return before the producer has put anything
Why it failsThe future is a place where a result will appear later. The outcome is only exposed once the promise has performed a put.
Do this insteadThink of get() as the point where the consumer takes delivery of whatever the promise eventually stores.
Checking the Handoff
A producing task has finished its work successfully and has a real result of type X. Which promise operation should it use to send that result to the future, and what does the consumer's get() call return? Then describe the same handoff for a task whose work failed after it caught an error. Which operation carries the error, and where does current_exception() fit in?
Hints
- Match the success case to the value path and the failure case to the exception path.
current_exception()is used inside the code that caught the error, and its result is what gets sent to the future.
Key Takeaways
- A promise is the sending side that matches a future's
get(). The promise provides the put operations, and the future providesget()to retrieve the result. - When sending a result of type X to a future, you pass either a value through
set_value()or an exception throughset_exception(), never both as a single outcome. current_exception()refers to a caught exception and is used to send that exception to a future through the promise.- The order of events is the same for values and exceptions: the producer puts an outcome into the promise, the future stores it, and the consumer's
get()exposes it.