Skip to Content
Course content

111: Futures, Promises, and Async Tasks

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I remember a developer I mentored a few years back who was building a telemetry dashboard. He needed to fetch data from three different remote sensors, each with a varying response time. His first instinct was to spawn three std::thread objects, but he quickly hit a wall: how do you actually get a value back from a thread? He ended up creating a shared std::vector protected by a std::mutex, and spent two days debugging a race condition where the main thread was trying to render the dashboard before the sensors had actually pushed their data into the vector.

He was doing it the hard way. When you just need a value from an asynchronous operation, you don't want to manage the low-level plumbing of threads and mutexes. That's where futures, promises, and async tasks come in. They provide a higher-level abstraction that lets you treat an asynchronous result as a value that simply "isn't here yet."

Offloading Work with std::async and std::future

The simplest way to start is with std::async. Think of it as a way to tell the runtime, "Run this function whenever you can, and give me a ticket I can use to claim the result later." That ticket is the std::future.

Let's look at a scenario where we're aggregating stock prices from different exchanges. Instead of blocking the main thread while waiting for a slow network response, we can fire off several async tasks:

#include <iostream>
#include <future>
#include <string>
#include <vector>

double fetchPrice(std::string exchange) {
    // Simulate a slow network call
    if (exchange == "NYSE") return 150.25;
    if (exchange == "NASDAQ") return 152.10;
    return 0.0;
}

int main() {
    // We launch these asynchronously. 
    // std::launch::async ensures they run in a separate thread.
    std::future<double> nysePrice = std::async(std::launch::async, fetchPrice, "NYSE");
    std::future<double> nasdaqPrice = std::async(std::launch::async, fetchPrice, "NASDAQ");

    // The main thread can do other work here...
    std::cout < "Processing other dashboard elements..." < std::endl;

    // Now we need the values. .get() blocks until the result is ready.
    double total = nysePrice.get() + nasdaqPrice.get();
    std::cout < "Average Price: " < total / 2 < std::endl;

    return 0;
}

One thing to be careful about: if you don't specify std::launch::async, the implementation might choose std::launch::deferred, which means the function won't actually run until you call .get(). That completely defeats the purpose of concurrency, so I usually recommend being explicit about your launch policy.

Manual Control with std::promise

While std::async is great for "fire and forget" functions, sometimes you need more control. Maybe you have a complex event loop or a callback-based API where the result is produced by some other piece of logic entirely. This is where std::promise comes in.

If a std::future is the "ticket" to claim the value, the std::promise is the "counter" where the value is actually handed over. You create a promise, extract a future from it, and pass the promise to the worker thread. The worker thread can then fulfill that promise at any point in its execution.

I find this particularly useful when you're integrating C++ with an external C library that uses callbacks. You can pass the promise into the callback's user-data pointer, and when the C library finally triggers the callback, you set the value in the promise. The rest of your C++ code, which is holding the future, doesn't need to know anything about the messy callback logic; it just calls .get() and waits for the result.

Keep in mind that calling .get() on a future is a one-time deal. Once you've retrieved the value, the future is invalidated. If you need multiple threads to wait for the same result, you'll want to look into std::shared_future, but for most standard task-based parallelism, the basic promise-future pair is all you'll need.




📋 Practical Task

Exercise: Parallel File Checksum Aggregator

You need to build a tool that calculates the "checksum" (for this exercise, a simple sum of all bytes) of multiple files in parallel. Using std::async, implement a program that:

  • Defines a function long long calculateChecksum(std::string filename) that opens a file in binary mode and returns the sum of all its bytes.
  • Takes a list of filenames (you can hardcode a vector of strings for this).
  • Launches a std::async task for each file.
  • Stores the resulting std::future<long long> objects in a std::vector.
  • Iterates through the vector of futures, calls .get() on each, and aggregates the final total checksum for all files.

Make sure you use std::launch::async to ensure the files are processed concurrently rather than sequentially.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.