Skip to content

C++20 Coroutine

可暫停函數

從最簡單的開始,假如我們希望下面這個東西可以跑起來

cpp
/* WHAT? */ coFoo() {
    std::cout << 1 << std::endl;
    // return to caller
    std::cout << 2 << std::endl;
}
  1. 呼叫 coFoo
  2. 沒暫停
  3. 印出 1
  4. 暫停,等待 resume
  5. 印出 2
  6. 沒暫停,直接結束

我們要先準備一個 coFoo return 的東西:

cpp
#include <coroutine>

struct Dummy {
	// 一個用來讓 caller 和 callee 溝通的橋樑
    struct promise_type {
	    ~promise_type() {
            std::cerr << "~promise()" << std::endl;
        }

        Dummy get_return_object() {
          return Dummy(
              std::coroutine_handle<promise_type>::from_promise(*this));
        }

        auto initial_suspend() noexcept {
	        // 進入時不暫停
            return std::suspend_never{};
        }

        auto final_suspend() noexcept {
	        // 結束時不暫停
            return std::suspend_never{};
        }

        void unhandled_exception() {}
    };

    explicit Dummy(const std::coroutine_handle<promise_type>& handle)
        : handle_(handle) {}

    std::coroutine_handle<promise_type> handle_;
};

然後就可以這樣寫:

cpp
Dummy coFoo() {
    std::cout << 1 << std::endl;
    co_await std::suspend_always{};
    std::cout << 2 << std::endl;
}

int main() {
    std::cout << "start" << std::endl;

    auto g = coFoo();

	// 印出 1 ,因為我們 initial_suspend 回傳的是 suspend_never

    std::cout << "main" << std::endl;

	// handle_ 是和 coroutine 互動的橋樑,呼叫 resume 就是 resume 該 coroutine。
    g.handle_.resume();

	// 印出 2
	// 印出 ~promise() ,因為我們 initial_suspend 回傳的是 suspend_never

    std::cout << "main" << std::endl;

    std::cout << "exit" << std::endl;

    return 0;
}

理解 Promise

他的機制可以直接這樣理解:

cpp
co_await promise.initial_suspend();
try {
  <body-statements>
} catch (...) {
  promise.unhandled_exception();
}
co_await promise.final_suspend();

中間收割(yield)一些值

現在換成一個有不斷 yield 的函數:

cpp

Generator foo() {
    co_yield 1;
    co_yield 2;
    co_yield 3;
}

我們在 Promise 裡面就需要多支援 yield_value:

cpp
int val_;
auto yield_value(int v) noexcept {
	val_ = v;
	return std::suspend_always{};
}

在外面要操作 Coroutine 就可以使用這三條:

  • handle_.done(): 回傳 coroutine 是否結束
  • handle_.resume(): 回去 coroutine 繼續跑
  • handle_.promise().val_: 取得暫存在 Promise 裡面 yield 到的數值。
cpp
class Generator {
public:
    struct Promise;
    using promise_type = Promise;
    using Handle = std::coroutine_handle<Promise>;

    struct Promise {
        int val_;

        Generator get_return_object() {
            return Generator(Handle::from_promise(*this));
        }

        auto initial_suspend() noexcept {
            return std::suspend_never{};
        }

        auto final_suspend() noexcept {
            return std::suspend_always{};
        }

        auto yield_value(int v) noexcept {
            val_ = v;
            return std::suspend_always{};
        }

        void unhandled_exception() {
        }
    };

    explicit Generator(const Handle& handle): handle_(handle) {
    }

    ~Generator() {
        if (handle_) {
            handle_.destroy();
        }
    }

	int get() const {
        return handle_.promise().val_;
    }

    bool done() const {
        return handle_.done();
    }

    void next() {
        if (!handle_.done()) {
            handle_();
        }
    }

private:
    Handle handle_;
};

Reference

Changelog

Just observe 👀