从异步封装角度解读c++20 协程

协程如果从头开始写,那么确实写起来比较简单。但是现存大量第三方库,基本清一色异步回调代码,那么怎么转换为c++20协程呢?

转换思路

用awaitable 包装现存异步操作,在回调完成时,resume 协程(coro_handle_.resume();)。其中每次async调用都发起一个异步操作,同时返回一个awaitable对象,用于co_await 操作。

示例代码

#include <chrono>
#include <coroutine>
#include <functional>
#include <iostream>
#include <queue>
#include <string>
#include <thread>

using task = std::function<void(const std::string&)>;

class executor {
private:
    std::thread work_thread_;
    std::queue<task> tasks_;

public:
    void add_task(task t) { tasks_.emplace(std::move(t)); }

    void run() {
        work_thread_ = std::thread([this]() {
            uint64_t times = 1;
            std::cout << "executor thread: " << std::this_thread::get_id()
                      << "\n";
            while (true) {
                auto& task = tasks_.front();
                task(std::to_string(times));
                times++;
                tasks_.pop();

                using namespace std::chrono_literals;
                std::this_thread::sleep_for(1000ms);
            }
        });

        if (work_thread_.joinable()) {
            work_thread_.join();
        }
    }
};

class coroutine_task {
public:
    struct promise_type {
        std::suspend_never initial_suspend() noexcept { return {}; }

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

        void unhandled_exception() noexcept {}

        coroutine_task get_return_object() noexcept {
            auto tk = coroutine_task{
                std::coroutine_handle<promise_type>::from_promise(*this)};
            return tk;
        }

        /*void return_value(int v) noexcept {}*/
        void return_void() noexcept {}

        std::suspend_always yield_value(std::string&& from) noexcept {
            value_ = std::move(from);
            return {};
        }
        std::string value_;
    };

private:
    std::coroutine_handle<promise_type> coro_;

public:
    coroutine_task(std::coroutine_handle<promise_type> h) : coro_(h) {}

    ~coroutine_task() {
        if (coro_) {
            coro_.destroy();
        }
    }

    std::string value() { return coro_.promise().value_; }
};

template <typename R, typename Executor = executor>
struct awaitable {
private:
    R buf_;
    Executor& e_;
    std::coroutine_handle<> coro_handle_;

public:
    awaitable(Executor& e) : e_(e) {}

    bool await_ready() noexcept { return false; }

    R await_resume() noexcept { return buf_; }

    void await_suspend(std::coroutine_handle<> p) noexcept {
        coro_handle_ = p;
    }

    void async_start() {
        e_.add_task([this](const R& times) {
            std::cout << "async resume thread: " << std::this_thread::get_id()
                      << "\n";
            buf_ = times;
            coro_handle_.resume();
        });
    }
};

auto async_read(executor& e) {
    awaitable<std::string> aw{e};
    aw.async_start();
    return aw;
}

coroutine_task coro_read1(executor& e) {
    std::cout << "coro_read1 begin thread: " << std::this_thread::get_id()
              << "\n";
    for (;;) {
        auto value = co_await async_read(e);
        std::cout << "1、coro_read1: " << value
                  << " thread: " << std::this_thread::get_id() << "\n";
        value = co_await async_read(e);
        std::cout << "2、coro_read1: " << value
                  << " thread: " << std::this_thread::get_id() << "\n";
    }
}

coroutine_task coro_read2(executor& e) {
    std::cout << "coro_read2 begin thread: " << std::this_thread::get_id()
              << "\n";
    for (;;) {
        auto value = co_await async_read(e);
        std::cout << "coro_read2: " << value
                  << " thread: " << std::this_thread::get_id() << "\n";
    }
}

int main() {
    executor e;

    auto cr1 = coro_read1(e);
    auto cr2 = coro_read2(e);

    e.run();
    return 0;
}

代码核心思路

其中async_read每次调用都发起一个异步操作(用线程模拟异步),同时返回一个awaitable对象,用于co_await 操作。
由此包装成 协程操作。async_read完成,则从"切入点"的下一行开始执行,打印协程返回值和当前运行的线程id。

注意事项

  1. cr1和cr2 表示协程“对象”,在协程运行期间不能被析构,否则意味着协程“消失”
  2. await_resume的返回值 表示 co_await 的返回值
  3. 协程在哪个线程resume的,后续代码则会在那个线程中运行。第一次会出现 “切入点” 前后代码在不同线程中运行的有意思现象
  4. coro_.destroy(); 只能作用于当前已经挂起的协程。如果是 std::suspend_never final_suspend(),则会出错。通常情况下,是不需要调用destroy函数的,协程frame会在协程结束销毁。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值