Concepts / Launching Tasks in C++

Launching Tasks in C++

The packaged_task connects the work to the result. It is the piece that makes the handle meaningful, because the handle is the only place the code that launched the work needs to look to receive the outcome.

  • Programming

Two Questions Behind Background Work

When a program runs work in the background, two questions come up right away. The first is how the work gets started. The second is how the result of that work gets back to the code that needs it. These two questions are easy to answer for an ordinary function call, and seeing why makes the rest of this article much clearer.

What do you think happens?

In an ordinary function call, how does the caller get the function's result?

Reveal answer
Answer:

The caller waits while the function runs and receives the return value directly.

A normal call answers both questions at once: it starts the work and delivers the result in the same step. A task that runs separately does not give you that shortcut, so something has to be set up to connect the launch to the returned result.

This article looks at two C++ tools that address this gap. The first is packaged_task, which helps launch tasks and connects up the mechanisms for returning a result. The second is async(), which launches a task in a manner very similar to calling a function. As you read, keep one question in mind: at each step, where is the work, and where is the result?

How packaged_task Links a Task to Its Result

The key word in the definition of packaged_task is connects. A packaged_task is not only a way to start work. It also sets up the path along which the outcome of that work travels back to the code that asked for it. The task and the returned result are linked through the packaged_task, so the code that launches the work and the code that eventually uses the result do not have to share the work itself.

Three pieces are involved. There is the callable, which is the work to be performed. There is the packaged_task, which wraps that callable and serves as the point where launch and connection happen. And there is the handle, the object from which the result can later be read. The diagram below shows how these three pieces relate to one another and to the returned value.

packaged_tasklaunch and connection pointcallablethe work to performreturned valueproduced when the taskfinishesargumentsinputs to the workhandleretrieves the result laterwrapstakesprovidesproducesdelivered to
How are the packaged_task, the callable it wraps, and the handle that receives the result linked, and which object holds the value once the task finishes?

Read the diagram from the top down. The packaged_task holds the callable, and the callable is what eventually produces the value. The value does not sit in the packaged_task. It is delivered to the handle, which is the object the requesting code keeps. This is why the handle is the answer to the question of where the result is: the work and the result are kept apart, and the handle is the link between them.

Key point

The packaged_task connects the work to the result. It is the piece that makes the handle meaningful, because the handle is the only place the code that launched the work needs to look to receive the outcome.

State Before and After a packaged_task Launch

The source emphasizes that creating a task and launching a task are different steps. With packaged_task, you build the task explicitly, obtain the handle for its result, and decide when to launch it. The handle is created before the work is launched. The path for the answer is set up first, and the launch step is what fills it.

Before the launch, the packaged_task exists as a prepared unit, and the handle already exists too. Nothing has produced a value yet, so the handle has nothing to hand over. After the launch, the same handle is still the object the caller holds, but now it is connected to work that is underway. The handle is the thing you keep; the value will eventually arrive through it.

How async() Mirrors a Function Call

The important phrase for async() is very similar to calling a function. With async(), you describe the work in the same shape you would use for an ordinary call: a callable followed by its arguments. The difference from a plain call is that the work is launched as a task rather than run inline, and you receive a handle through which the result can later be obtained.

Worked example

Launching a square computation with async()

Suppose a square function computes the square of its argument, and you want to run it as a task with the argument 7. What does the code that launches it hold afterward?

  1. 1
    Describe the work

    You provide the callable, which is the square function, and its argument, which is 7. This is the same shape as an ordinary call.

  2. 2
    Make one call

    A single call to async() both launches the task and hands back a handle. There is no separate wrapping step and no separate launch step to write.

  3. 3
    Hold the handle

    The calling code now holds a handle, not the number 49 itself. The result is retrieved from that handle later.

  4. 4
    Read the result later

    When the code needs the outcome, it reads the value through the handle. The value of the square, 49, is what the task produces.

Result

The caller holds a handle to a task that computes 49, and the state to track is small: one call, one handle.

The source notes that the state you should track is much smaller with async(), because the call itself carries both the launch and the handle. You provide the callable and the argument, and you receive a handle to retrieve later. The program does not need a separate wrapping step or a separate launch step.

Key point

Calling async() looks like calling a function, but the result comes back as a handle rather than as a direct return value. The work runs as a task, and the handle is how the answer is retrieved.

Comparing Control Over Launch and Result

Both mechanisms deal with the same underlying need, which is running a task and getting its result back. They differ in where the control sits. The diagram below places the two side by side so you can see which steps each one folds together.

launch stepseparate and explicitlaunch stepfolded into one callhandleobtained before launchhandlereturned by the callcontrolyou decide when to launchcontrolone call does both
Where do the two mechanisms differ in how the launch is performed, how the handle is obtained, and who decides when the work starts?
Aspectpackaged_taskasync()
Launch stepKept separate from building the taskFolded into the same call that returns the handle
HandleObtained from the task before launching itReturned by the call that launches the task
Shape of codeBuild, get handle, then launchLooks much like an ordinary function call
Where control sitsWith the programmer, who chooses when to launchWith the single call that launches and returns the handle
Tracked stateTask, handle, and a later launch stepCallable, arguments, and one returned handle

Contrasting the two launch mechanisms, based on the course source

Choosing Between the Two Mechanisms

The choice depends on how much control you need over the launch step. Use the following guide when deciding.

  • Choose packaged_task when you need to build the task explicitly, obtain its handle, and decide yourself when the launch happens.
  • Choose async() when you want the launch to look like an ordinary function call, with a callable and its arguments passed in one step and a handle returned for the result.
  • Choose async() when the requirement is a simple computation whose result you will collect later, since the call carries both the launch and the handle.
  • Choose packaged_task when the connection between launch and result needs to be set up first and filled in later, because the handle is created before the work is launched.

Mistakes Learners Make with Launched Tasks

  • Common mistake

    Treating async() as a plain function call that returns the value directly

    What it looks like

    Expecting the call that uses the square function with 7 to produce the number 49 right away.

    Why it fails

    The source says async() launches a task in a manner very similar to calling a function, but the result is retrieved through the handle that the call provides.

    Do this instead

    Keep the handle, and read the result from it when the code needs the outcome.

  • Common mistake

    Assuming the result is stored inside the packaged_task

    What it looks like

    Looking for the answer inside the packaged_task object after the task has been launched.

    Why it fails

    The packaged_task connects the work to the result, and the value reaches the handle that the requesting code holds.

    Do this instead

    Read the outcome from the handle, the object the launching code keeps.

  • Common mistake

    Believing the handle appears only after the result is ready

    What it looks like

    Waiting for the result before deciding what to store as the handle.

    Why it fails

    The source states the handle is created before the work is launched, which is how the answer path is set up first.

    Do this instead

    Obtain the handle first, then launch the work, so the launch fills a path that already exists.

  • Common mistake

    Forgetting that the choice between the two is about control over the launch step, not about whether the result is delivered

Trace the State Yourself

MEDIUM

A program builds a packaged_task for a computation and obtains its handle, but it has not yet launched the task. Describe which objects exist at this moment and what the handle is waiting for. Then describe what changes when the task is launched.

Hints
  • Recall that the handle is created before the work is launched.
  • Think about which piece holds the callable and which piece will eventually hold the outcome.
  • After the launch, what is the handle waiting for?
EASY

Compare two requirements: one needs the launching code to decide exactly when the work starts, and one needs a simple background computation whose result is collected later. Which mechanism fits each, and what state does each leave you holding?

Hints
  • Ask who controls the launch step in each mechanism.
  • Ask which mechanism gives you the handle in one call.

Key Takeaways

  • packaged_task launches tasks and connects the launch to a returned result, so the work and the outcome are linked through the packaged_task and its handle.
  • The handle for a packaged_task result is created before the work is launched, and the launch step is what fills that path.
  • async() launches a task in a manner very similar to calling a function, taking a callable and its arguments in one call and returning a handle.
  • Choose packaged_task when you need explicit control over building the task and deciding when it launches; choose async() when a single call that looks like a function call is enough.

Related concepts

More concepts starting with L