Asynchronous programming for coroutine in C++20

Huang Kevin
3 min readJun 8, 2022

Two minimal examples

Image from Unsplash

In recent years, coroutine has gained more and more attention in C++ community. It has been incorporated into C++20, and soon more elaboration in C++23. When I surfed the internet to study this concept in C++, I felt quite frustrated to find a good introductory article to help beginner to grasp basic mechanism of coroutine in C++20. To help those who find it hard to identify the basic pattern of coroutine amid nitty-gritty of complicated code, I will show two minimal examples from ref[1,2] to elucidate basic and reusable structure of coroutine in this post.

Minimal structure: awaitable and task

The first example[1] I am going to show is from the book[2], which clearly show that two custom types, awaitable and task, are required to construct a coroutine. In awaitable, several things are defined: when coroutine should be suspended(await_ready), what to do when coroutine is suspended(await_suspend), and what to do when coroutine is resumed(await_resume).

struct awaitable {    
std::jthread& t3;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h) {
std::jthread& out = t3;
out = std::jthread([h] { h.resume(); });
std::cout << "New thread ID: " << out.get_id() << '\n';}
void await_resume() {}
~awaitable() {
...}
awaitable(std::jthread& t) : t3(t) {
... }};

In task, some other issues are included: what to do when the coroutine is suspended for the first and last time(initial_suspend, final_suspend), and what to return from coroutine(return_void).

struct task{    
struct promise_type {
task get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {} };
};

In addition to demonstration of basic structure, this example shows how a single coroutine could be suspended in one thread, while resumed at other thread.

Coroutine could be suspended in one thread, while resumed at other thread.

Minimal structure for thread synchronization

The second example[3] expands from the first one a little further, and uses coroutine to construct a minimal viable system for thread synchronization. To be specific, coroutine synchronizes sender thread and receiver thread. The former provides the data and notifies the event, which is a wrapper around awaitable, and the latter will resume coroutine if the event has been notified. As illustrated below, there are two possible synchronization pattern between and sender thread. Depending on the timing of thread initialization, coroutine could be processed in single or two threads.

There are two possible synchronization pattern between receiver and sender thread.

Conclusion

Two basic examples about asynchronous programming via coroutine in C++20 are introduced in this post. Basically, the execution of coroutine could be switched between different threads via two fundamental building blocks — awaitable and task, without some drawbacks from condition variable[3]. In the end, this post only serves as the beginning to the learning path toward coroutine. There are some important and advanced usages of coroutine, including thread pool and cppcoro library, missing in this post. I hope I can write about these concepts in the future.

Reference:

  1. https://github.com/PacktPublishing/The-Art-of-Writing-Efficient-Programs/blob/master/Chapter08/coroutines_change_threads.C
  2. Pikus, F. G. The Art of Writing Efficient Programs: An Advanced Programmer s Guide to Efficient Hardware Utilization and Compiler Optimizations Using C++ Examples; 2021.
  3. https://www.modernescpp.com/index.php/c-20-thread-synchronization-with-coroutines

--

--

Huang Kevin

Algorithm engineer at semiconductor company with background in physics