Asynchronous Programming in C++: A Complete Practical Guide
Asynchronous programming in C++ is an approach that allows you to execute multiple tasks in parallel without blocking the main execution thread. Unlike synchronous code, where each operation waits for the previous one to complete, asynchronous code can initiate long-running operations (I/O, network requests, computations) and continue executing other tasks while these operations complete in the background. This is critical for creating responsive applications, especially in the gaming industry, web servers, and real-time systems.
In this article, we will explore asynchronous programming in C++ in practice: from the classic mechanisms std::async and std::future to modern C++20 coroutines. You will learn how to correctly use these tools, avoid common mistakes, and build efficient asynchronous systems.
1. Asynchrony Basics: std::async and std::future
The C++11 standard library provides a simple way to launch asynchronous tasks — the std::async function and the std::future class. std::async runs a function in a separate thread (or thread pool) and returns a std::future that holds the result of the execution.
Example 1: Basic Launch of an Asynchronous Task
#include <iostream>#include <future>#include <thread>#include <chrono>
int longComputation(int n) { std::this_thread::sleep_for(std::chrono::seconds(2)); return n * n;}
int main() { std::cout << "Launching asynchronous task..." << std::endl; std::future<int> result = std::async(std::launch::async, longComputation, 10);
// While the task is running, other work can be done std::cout << "Main thread continues working..." << std::endl; std::this_thread::sleep_for(std::chrono::seconds(1));
// Getting the result (blocks the thread if the task is not yet complete) int value = result.get(); std::cout << "Result: " << value << std::endl; return 0;}Important points:
std::launch::asyncguarantees execution in a separate thread.result.get()blocks the current thread until the result is obtained.- If
get()is not called, the destructor ofstd::futuremay block execution until the task completes.
Example 2: Exception Handling in Asynchronous Tasks
#include <iostream>#include <future>#include <stdexcept>
int riskyFunction(int x) { if (x < 0) throw std::runtime_error("Negative number!"); return x * 2;}
int main() { std::future<int> future = std::async(std::launch::async, riskyFunction, -5); try { int result = future.get(); std::cout << "Result: " << result << std::endl; } catch (const std::exception& e) { std::cout << "Exception: " << e.what() << std::endl; } return 0;}2. Advanced Work with std::future and std::promise
std::promise is a tool for manually managing asynchronous results. You can set a value or exception in one thread and retrieve it via std::future in another. This is useful for implementing custom asynchronous patterns, such as passing data between threads.
Example 3: Using std::promise to Pass Data
#include <iostream>#include <future>#include <thread>
void producer(std::promise<std::string> prom) { std::this_thread::sleep_for(std::chrono::seconds(1)); prom.set_value("Data is ready!");}
int main() { std::promise<std::string> promise; std::future<s