Task Based Programming Over Thread Base

Published: (March 6, 2026 at 07:06 PM EST)
2 min read
Source: Dev.to

Source: Dev.to

Prefer Task‑Based Programming to Thread‑Based

If you want to run something asynchronously you have two basic choices: std::thread and std::async.

int doAsyncWork();

std::thread th(doAsyncWork); // thread‑based
auto future = std::async(doAsyncWork);

std::async(doAsyncWork) creates a task.
The task is superior because it returns a std::future that can be used to synchronize the program. The future provides a get() function to retrieve the result or re‑throw any exception thrown by the work function. Tasks also offer a higher level of abstraction.

What is a Thread?

  • Hardware threads – the physical execution units provided by the CPU. Modern CPUs may expose one or more hardware threads per core.
  • Software threads (OS or system threads) – abstractions managed by the operating system, scheduled onto hardware threads.
  • std::thread objects are handles to underlying software threads.

Software threads are a limited resource. Creating more threads than the system can provide throws a std::system_error (even from a noexcept function).

Even when you don’t exhaust the thread pool, oversubscription can occur: more ready‑to‑run software threads than available hardware threads, leading to frequent context switches and increased overhead.

Why Prefer std::async?

std::async delegates the management of thread creation and scheduling to the implementation. A task created with std::async typically avoids the oversubscription problem because it does not guarantee the creation of a new software thread. Instead, the scheduler may run the function on an existing thread when the result is needed.

When the default launch policy is used, the implementation may choose between asynchronous execution (new thread) or deferred execution (same thread).

Launch Policies

PolicyBehaviour
std::launch::asyncThe function is guaranteed to run on a new thread.
std::launch::deferredThe function does not run immediately. It runs only when future.get() or future.wait() is called, executing on the calling thread.
Default (std::launch::async | std::launch::deferred)The implementation decides whether to run the task asynchronously (new thread) or defer it (same thread, lazy execution).

Using std::async with an appropriate launch policy lets you write clearer, safer asynchronous code without manually handling thread lifetimes and oversubscription concerns.

0 views
Back to Blog

Related posts

Read more »

Runs vs. Threads: When to Use Which

markdown !Cover image for Runs vs. Threads: When to Use Whichhttps://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%...