C++ Coroutines | Asynchronous Programming in C++20

C++ Coroutines | Asynchronous Programming in C++20

이 글의 핵심

Overview of C++ coroutines: promise types, suspension, and practical patterns with code.

Coroutine basics

#include <coroutine>
#include <iostream>
using namespace std;

struct Task {
    struct promise_type {
        Task get_return_object() {
            return Task{handle_type::from_promise(*this)};
        }
        
        suspend_never initial_suspend() { return {}; }
        suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
    
    using handle_type = coroutine_handle<promise_type>;
    handle_type coro;
    
    Task(handle_type h) : coro(h) {}
    ~Task() { if (coro) coro.destroy(); }
};

Task simpleCoroutine() {
    cout << "Coroutine start" << endl;
    co_return;
}

int main() {
    simpleCoroutine();
}

Generators

#include <coroutine>
#include <iostream>
using namespace std;

template<typename T>
struct Generator {
    struct promise_type {
        T current_value;
        
        Generator get_return_object() {
            return Generator{handle_type::from_promise(*this)};
        }
        
        suspend_always initial_suspend() { return {}; }
        suspend_always final_suspend() noexcept { return {}; }
        
        suspend_always yield_value(T value) {
            current_value = value;
            return {};
        }
        
        void return_void() {}
        void unhandled_exception() {}
    };
    
    using handle_type = coroutine_handle<promise_type>;
    handle_type coro;
    
    Generator(handle_type h) : coro(h) {}
    ~Generator() { if (coro) coro.destroy(); }
    
    bool move_next() {
        coro.resume();
        return !coro.done();
    }
    
    T current_value() {
        return coro.promise().current_value;
    }
};

Generator<int> counter(int max) {
    for (int i = 0; i < max; i++) {
        co_yield i;
    }
}

int main() {
    auto gen = counter(5);
    
    while (gen.move_next()) {
        cout << gen.current_value() << " ";  // 0 1 2 3 4
    }
}

co_await

struct Awaitable {
    bool await_ready() { return false; }
    void await_suspend(coroutine_handle<>) {}
    void await_resume() {}
};

Task asyncFunction() {
    cout << "Start" << endl;
    co_await Awaitable{};
    cout << "Resumed" << endl;
}

Practical examples

Example 1: Fibonacci generator

Generator<int> fibonacci(int n) {
    int a = 0, b = 1;
    
    for (int i = 0; i < n; i++) {
        co_yield a;
        int temp = a;
        a = b;
        b = temp + b;
    }
}

int main() {
    auto fib = fibonacci(10);
    
    while (fib.move_next()) {
        cout << fib.current_value() << " ";
    }
}

Example 2: Range generator

Generator<int> range(int start, int end, int step = 1) {
    for (int i = start; i < end; i += step) {
        co_yield i;
    }
}

int main() {
    for (auto gen = range(0, 10, 2); gen.move_next();) {
        cout << gen.current_value() << " ";  // 0 2 4 6 8
    }
}

Example 3: Read file lines

#include <fstream>

Generator<string> readLines(const string& filename) {
    ifstream file(filename);
    string line;
    
    while (getline(file, line)) {
        co_yield line;
    }
}

int main() {
    auto lines = readLines("input.txt");
    
    while (lines.move_next()) {
        cout << lines.current_value() << endl;
    }
}

Example 4: Async timer

#include <chrono>
#include <thread>

struct Timer {
    chrono::milliseconds duration;
    
    bool await_ready() { return false; }
    
    void await_suspend(coroutine_handle<> h) {
        thread([h, d = duration]() {
            this_thread::sleep_for(d);
            h.resume();
        }).detach();
    }
    
    void await_resume() {}
};

Task asyncTask() {
    cout << "Start" << endl;
    
    co_await Timer{chrono::seconds(1)};
    cout << "After 1 second" << endl;
    
    co_await Timer{chrono::seconds(1)};
    cout << "After 2 seconds total" << endl;
}

Coroutines vs threads

// Thread (heavier)
void threadExample() {
    thread t([] {
        // work
    });
    t.join();
}

// Coroutine (lighter)
Task coroutineExample() {
    co_await someOperation();
    // work
}

Differences:

  • Thread: OS resource, context-switch cost
  • Coroutine: user space, lightweight suspend

Common problems

Problem 1: Missing promise_type

// Error without promise_type
Task myCoroutine() {
    co_return;
}

// Define promise_type inside Task
struct Task {
    struct promise_type {
        // ...
    };
};

Problem 2: Forgetting to destroy the handle

// Leak if destructor omits destroy
Generator<int> gen = counter(10);

// Destructor should call coro.destroy()
~Generator() {
    if (coro) coro.destroy();
}

Problem 3: Local variable lifetime

Task bodies store locals in the coroutine frame; still avoid dangling references to outer stack objects across suspension points without copying.

Coroutine state

coroutine_handle<> h = ...;

h.resume();
h.done();
h.destroy();
h.promise();

FAQ

Q1: When should I use coroutines?

A: Async I/O, generators, state machines, cooperative multitasking.

Q2: Coroutines vs async/await in other languages?

A: C++ coroutines are low-level; libraries like cppcoro add ergonomics.

Q3: Performance?

A: Much lighter than OS threads; many concurrent coroutines are feasible.

Q4: Compiler support?

A: GCC 10+, Clang 14+, MSVC 2019+.

Q5: Debugging?

A: Harder than plain functions; add logging; debugger support is limited.

Q6: Learning resources?

A: cppreference.com, “C++20: The Complete Guide”, cppcoro, Lewis Baker blog.


Practical tips

Debugging

  • Check compiler warnings first
  • Reproduce with a small test case

Performance

  • Profile before optimizing

Code review

  • Follow team conventions

Production checklist

Before coding

  • Best fit for the problem?
  • Maintainable by the team?
  • Meets performance needs?

While coding

  • Warnings cleared?
  • Edge cases handled?

At review

  • Intent clear?
  • Tests sufficient?

Keywords

C++, coroutine, C++20, async, asynchronous


  • C++ series
  • C++ Adapter Pattern
  • C++ ADL
  • C++ aggregate initialization