Spawning Tasks on Separate Threads
A future is a mechanism for returning a value from a task spawned on a separate thread.
Why the Caller Cannot Wait Empty-Handed
When a task is spawned on a separate thread, the code that started it keeps running. The task may finish some time later, but the caller still needs its result. This creates a practical problem: how does a value produced on one thread get back to the code that needs it on another thread?
The source introduces futures and promises as the mechanism for this job. As you read, keep one question in mind throughout: at any moment, is the value already available, or is it still on its way? Most of the reasoning about futures and promises comes down to answering that question correctly.
The Claim Ticket Analogy
Think of a task on a separate thread as a piece of work you hand to someone else. You do not get the answer back immediately. Instead, you receive a claim ticket that will be honored when the answer exists. The ticket is not the answer. It is the thing you can check and collect from later.
What a Future Does
A future is a mechanism for returning a value from a task spawned on a separate thread.
Because the value is produced on a separate thread, the future is the natural place to wait for that value and collect it once it is ready. Its purpose is narrow and specific: moving a result from a task back to the code that needs it. The future is the handle the caller holds. It is the thing the caller reads from.
What a Promise Does
A promise works together with a future for handling the task's return value. The useful way to keep the two ideas apart is this: the future is what you read from, and the promise is what the task uses to make the value available. Neither one is useful on its own in this pattern. The value travels from one to the other.
The relationship is a linked pair. The task running on a separate thread is connected to a promise, the promise is linked to a future, and the future returns the value to the caller. Reading the chain from left to right in terms of data flow: the task is where the value comes from, the promise is the channel that carries it, the future is the handle the caller holds, and the calling code is where the value is finally used.
Tracing the Pair from Waiting to Read
The pair moves through a small number of states. It begins in a waiting state, in which the future has no value yet. The only change that moves it forward is the value being supplied through the promise. Once that happens, the future has something to return. Reading it is the final step. The diagram below traces these states in order.
Notice that the waiting state is the first state, and it is the one learners most often underestimate. Nothing has gone wrong while the future is waiting. Only the supply of the value through the promise moves the pair forward, and only reading the future completes the trace.
Following the Value Across Threads
The value has a direction. It starts in the spawned task, moves through the promise, is held by the future, and is finally used by the calling code. The next diagram shows only that movement, without the state labels.
A useful check is to ask, at each arrow, whether the value has already crossed it. Before the task supplies the value, nothing has crossed the first arrow, so the future has nothing to return.
Two Threads Running Side by Side
Once the task is spawned, the caller and the spawned task proceed on their own threads. The code that started the task keeps running, so the two threads move forward independently until the caller needs the result. The timeline below shows one possible ordering of these events. The order of the spawned task's finish relative to the caller's progress can vary, which is exactly why the caller must check whether the value is available before using it.
Worked Trace of One Handoff
Tracing a single handoff from spawn to read
A caller spawns a task on a separate thread. The caller later needs the task's return value. Trace the state of the future-and-promise pair at each point: right after spawning, after the value is supplied, and after the caller reads it.
- 1Right after spawning
The caller receives a future immediately, as a placeholder. The pair is in its waiting state because no value has been supplied through the promise yet. This is expected, not an error.
- 2While the task runs
The caller keeps running. The task on the separate thread is still producing its result. Nothing has yet moved through the promise, so the future still has nothing to return.
- 3After the value is supplied
The task delivers its value through the promise. The future now has something to return. The pair has left the waiting state and is ready for the caller to read.
- 4After the caller reads it
The caller reads the future and collects the value. Reading the future is the final step in the trace.
The pair moves from waiting, to a value being ready, to the value being read. The only event that moves it forward from waiting is the value being supplied through the promise.
What do you think happens?
Right after the task is spawned, and before it has supplied any value, is the future in an error state?
Reveal answer
No, a waiting future is a valid and expected state
The source is explicit that a future may be waiting for its value, and that this waiting state is expected rather than an error. Only the arrival of the value through the promise moves the pair forward.
Mistakes Learners Make with Futures
Common mistake Treating the future as the answer itself
What it looks likeReading a future right after spawning the task and assuming the result is already in hand.
Why it failsThe source says the ticket is not the answer. The future is the thing you check and collect from later, and the value may not have arrived yet.
Do this insteadRecognize that the future is a handle. Check whether the value is available before relying on it.
Common mistake Reading a waiting future as an error
What it looks likeConcluding that something broke because the future has no value yet.
Why it failsA future that has not yet received its value is an expected situation. Waiting is part of the normal flow of the pattern.
Do this insteadTreat the waiting state as the first valid state in the trace, not as a failure.
Common mistake Thinking the future alone moves the value
What it looks likeAssuming the caller's read is what causes the value to be produced.
Why it failsThe value comes from the task and travels through the promise. The future is only where the caller holds and collects it.
Do this insteadFollow the value from the task, through the promise, to the future, and finally to the caller.
Common mistake Forgetting that the caller keeps running after the task is spawned
Practice Tracing a Pair
A caller spawns a task on a separate thread and immediately receives a future. Describe the state of the future-and-promise pair at three moments: immediately after spawning, after the spawned task supplies its value through the promise, and after the caller reads the future. For each moment, say which event, if any, moved the pair into that state.
Hints
- Start from the waiting state, which is where the pair begins before any value arrives.
- Ask which single event moves the pair forward from waiting.
- The final state happens only after the caller reads the future.
Key Takeaways
- A future is a mechanism for returning a value from a task spawned on a separate thread. It is the handle the caller holds and reads from.
- A promise works together with a future. The task uses the promise to make the value available, and the value travels from the promise to the future.
- The pair begins in a waiting state. That state is expected and is not an error. Only the value being supplied through the promise moves the pair forward.
- After the task is spawned, the caller keeps running. The central question is always whether the value is already available or still on its way.
- The trace ends when the caller reads the future and collects the value.