Concepts / Transferring Values between Tasks

Transferring Values between Tasks

A future and a promise are related objects that together move a value from one task to another.

  • Programming

Why Tasks Need a Handoff

Picture two tasks running side by side. One of them computes something that the other one needs. The first task has to get its result to the second one, and that is the whole problem this article is about. A value produced in one task must travel to another task before the second task can use it.

Futures and promises are a mechanism designed for exactly this situation: moving a value from one task to another. What makes them interesting is the way they do it. Instead of asking the programmer to coordinate every hand-off by hand, the mechanism lets the value travel between tasks while the system takes care of the transfer itself.

In this article we follow a single value on its journey. We will look at the two objects involved, the direction the value moves in, and the reasoning you need so that you never mix up which side of the pair does what.

Following One Value on Its Journey

Before reading the mechanism, try to reason about it yourself. Suppose a producing task has computed a value, and a reading task needs that value. The value has to leave one task and arrive somewhere the other task can look. Think about which object you would expect the producing task to touch, and which object the reading task would look at.

What do you think happens?

When a producing task has a value to pass on, which object does it write that value into, and which object does the reading side look at?

Reveal answer
Answer:

The producing task places its value into a promise. The value then appears in the corresponding future, and the reading side reads it from the future.

The producing task's job ends at the promise. It never has to reach into the future itself. The future is the object the reading side looks at.

Promise and Future as a Linked Pair

Definition

A future and a promise are related objects that together move a value from one task to another. The promise is where the producing task puts the value. The future is the corresponding object where that value appears, and it is the object the reading side looks at.

Producing taskcomputes the valuePromisereceives the valueReading sidelauncher of the taskFuturevalue appears hereplaces value intocorresponds toreads value from
Which object does the producing task write to, which object does the reading task hold, and how are the two objects connected so the value crosses between them?

The two objects are a pair. The promise is on the writing side and the future is on the reading side. The source connects them through the implementation: whatever is placed into the promise shows up in the corresponding future.

The Three Moves from Producer to Reader

The basic idea can be stated in three moves. Each move changes where the value is. Follow the value through them in order.

  1. 1
    First, a task that wants to pass a value to another task places that value into a promise.
  2. 2
    Second, the implementation makes the value show up in the corresponding future.
  3. 3
    Third, the value can be read from that future. The launcher of the task is the usual reader.
Producing taskcomputes the valuePromisevalue placed inFuturevalue appearsReading sidelauncher reads valuemove 1move 2move 3
Step by step, where does the value travel from the task that computes it to the code that finally reads it?

Notice that the producing task never reaches into the future. Its responsibility stops at the promise. The step in the middle, where the value appears in the future, is handled by the implementation. The source says that the implementation makes the value appear there but does not describe how it does so. What you need to hold onto is the outcome: the value leaves the producing task through the promise and arrives at the future, where the reading side looks.

Why No Lock Appears in Your Code

The defining feature of futures and promises is that they move a value between two tasks without explicit use of a lock. In a setting where you might otherwise coordinate access to shared data with a lock that you acquire and release yourself, this mechanism removes that step from your code. You do not write the coordination; the system provides it.

Tracing One Value Through a Handoff

Worked example

Tracing a value from a producing task to a reader

Task A computes a value that Task B needs. Trace where the value is at each stage, and identify which object Task A touches and which object Task B looks at.

  1. 1
    Task A computes the value

    The value exists only inside the producing task at this point. Nothing has been handed off yet.

  2. 2
    Task A places the value into a promise

    This is the producing task's action. It writes to the promise and does not reach into the future.

  3. 3
    The implementation makes the value appear in the future

    The value now sits in the corresponding future. The source does not describe the mechanism, so the learner only tracks the outcome.

  4. 4
    The launcher of Task B reads the value from the future

    The reading side looks at the future. The launcher is the usual reader named by the source.

Result

The value moved from the producing task, through the promise, into the future, and was read by the reading side. Task A wrote to the promise, and the reading side read from the future.

Mixing Up Writer and Reader

Most errors with this mechanism come from reasoning about the wrong side of the pair. The promise and the future are linked, but they play opposite roles. The table below separates the two sides so you can check which object each party uses.

Producing taskLauncher of taskPromiseFuturewrites intoreads from
Which side of the promise and future pair is the writer and which is the reader, and what mix-ups happen when these roles are swapped in reasoning?
  • Common mistake

    Thinking the producing task reads from the future

    What it looks like

    A producing task that tries to fetch the value back out of the future after placing it in the promise.

    Why it fails

    The producing task's job ends at the promise. The future is the object the reading side looks at.

    Do this instead

    Keep the producing task on the write side: it places the value into the promise and stops there.

  • Common mistake

    Thinking the value is written directly into the future

    What it looks like

    Reasoning that the producing task should put its value straight into the future object.

    Why it fails

    The source says the task places the value into a promise, and the implementation makes it appear in the corresponding future.

    Do this instead

    Follow the order: promise first, then the value appears in the future.

  • Common mistake

    Assuming you must add a lock because the tasks share data

    What it looks like

    Writing lock acquire and release calls around the handoff.

    Why it fails

    The mechanism moves the value without explicit use of a lock, because the system implements the transfer.

    Do this instead

    Do not write the coordination yourself. Use the promise and the future as the source describes.

  • Common mistake

    Confusing the launcher of a task with the producing task, when the source names the launcher as the usual reader

Practice Tracing a Handoff

EASY

Two tasks are running. Task X computes a value that Task Y needs. Without looking back at the article, describe the path the value takes from Task X to Task Y. For each step, name the object involved and say whether it is on the writing side or the reading side.

Hints
  • Start with the producing task and ask where its value goes first.
  • Ask which object the producing task never touches.
  • Identify who the usual reader is, according to the source.

Key Takeaways

  • Tasks need a handoff mechanism because a value computed in one task is often needed by another, and futures and promises exist to move that value between tasks.
  • A promise is where the producing task places its value, and the corresponding future is where that value appears for the reading side.
  • The value travels in three moves: into the promise, then into the future, then read by the launcher of the task. The producing task stops at the promise.
  • The transfer happens without explicit use of a lock in your code, because the system implements it.
  • Most mistakes come from swapping the roles of the promise and the future, so keep the write side and the read side clearly separated in your reasoning.

More concepts starting with T