async(1): cpp20协程基础

本文详细介绍了C++20中的coroutine概念,包括Coroutine实例教学,强调了三个关键运算符co_await、co_yield和co_return的作用。讨论了等待体(Awaiter)的概念,协程的返回值类型要求,以及如何通过await_transform和yield_value处理表达式。还探讨了Task和通用异步任务的实现,以及协程调度、挂起、消息传递和Awaiter的使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


这篇讲的不错: C++20 Coroutine实例教学 - 知乎 (zhihu.com)

图:

https://pic1.zhimg.com/80/v2-5d11f4e5b9eb2b4f5e692b73baa69f78_720w.webp

从上图可以看出c++ coroutine20实现的几个特点:

  1. 依赖coroutine_handle<>对象管理协程本身的生命周期
  2. 依赖promise_type对象对协程的一些行为(如启动挂起, 执行结束前挂起等)进行配置, 传递返回值
  3. co_await机制配合Awaitable对象完成协程的挂起点定义以及协程与外界的数据交换。

三个运算符:

协程其实也是一个函数

区分协程与普通函数:只有以下三个运算符其中一个,则代表这个函数是协程

  • co_await
  • co_yeild
  • co_return

等待体

等待体(Awaiter)。co_await 后面接的对象都是Awaiter,需要实现三个函数:

  • bool await_ready(): co_wait awaiter时,先调用awaiter.await_ready() :如果返回 true,则表示已经就绪,无需挂起;如果返回 false, 表示需要挂起,则接着调用awaiter.await_suspend()

  • ??? await_suspend(std::coroutine_handle<> coroutine_handle): await_suspend 是协程的挂起点(suspend point),用于指定在协程挂起时要执行的操作参数 coroutine_handle 用来表示当前协程 . 注意到 await_suspend 函数的返回值类型我们没有明确给出,因为它有以下几种选项:

    • 返回 void 类型或者返回 true,表示当前协程挂起之后将执行权还给当初调用或者恢复当前协程的函数。
    • 返回 false,则恢复执行当前协程。注意此时不同于 await_ready 返回 true 的情形,此时协程已经挂起,await_suspend 返回 false 相当于挂起又立即恢复。
    • 返回其他协程的 coroutine_handle 对象,这时候返回的 coroutine_handle 对应的协程被恢复执行。
    • 抛出异常,此时当前协程恢复执行,并在当前协程当中抛出异常。
  • ??? await_resume():await_resume 是协程的一个特殊成员函数,用于指定在协程恢复执行时要返回的值。在 cpp 中,await_resume 函数通常用于异步操作的结果返回。当协程在 co_await 表达式处被暂停,并且异步操作完成后,协程将恢复执行,并调用与 co_await 表达式关联的 await_resume 函数来获取异步操作的结果。await_resume 函数应该返回一个值,这个值就是 co_await 表达式的结果。这个结果可以是任何类型,取决于你的异步操作的返回类型以及你希望在协程中使用的数据类型。

最简单的两个Awaiter

struct suspend_never {
    constexpr bool await_ready() const noexcept {
        return true;  // 返回 true,总是不挂起
    }
    ...
};

struct suspend_always {
    constexpr bool await_ready() const noexcept {
        return false; // 返回 false,总是挂起
    }

    ...
};

协程的返回值类型

协程的返回值类型Result:

我们要在c++20中定义一个coroutine, 对函数的返回值是有要求的, 这个返回值的类型必须有一个嵌套的子类型promise_type。

  • Result中必须要有一个类型promise_type, promise_type中必须实现以下函数:
    • initial_suspend(): initial_suspend() 是协程的一个特殊成员函数,用于指定在协程开始执行时是否立即暂停。在 cpp 中,initial_suspend() 函数返回一个 std::suspend_alwaysstd::suspend_never 类型的对象,决定了协程是否在开始执行时暂停。当然也可以返回其他自定义的Awaiter。
    • Result get_return_object():get_return_object() 的作用是为协程创建一个协程对象,并在协程开始执行之前,为其进行初始化和资源分配。get_return_object() 函数在协程的创建阶段被调用,用于创建与协程关联的协程对象,并在协程开始执行之前进行初始化和资源分配。
    • void return_value(int value)和void return_void():在co_returm时调用
    • void unhandled_exception():抛出异常
    • final_suspend():final_suspend() 函数返回一个表示协程最终挂起点的 awaitable 对象,通常是 std::suspend_neverstd::suspend_always。这个返回值告诉编译器在协程执行完毕后是否需要额外的挂起操作。

所以Result就是这样的:

class Result {
	struct promise_type {
    	  Awaitable initial_suspend() {
              // ...
              return Awaiter;
          }
        
          Awaitable final_suspend() {
              // ...
              return Awaiter;
          }
          
          Result get_return_object() {
              // ...
              return Result{std::coroutine_handle<promise_type>::from_promise(*this)};
          }
          
          void return_value(int value) { // value是co_return value时传入
              // ...
          }
        
          void unhandled_exception() noexcept { 
              // std::current_exception() 获取当前异常
              处理异常
          }
        	
          // 。。。。。。 
    };
};
  • static coroutine_handle from_promise(_Promise& _Prom) noexcept:通过 promise_type 的对象的地址获取 coroutine_handle 的函数,它实际上是 coroutine_handle 的一个静态函数。可以使用是这个函数保存当前协程的句柄。

  • 对于 co_await <expr> 表达式当中 expr 的处理,cpp 有一套完善的流程:

    • 如果 promise_type 当中定义了 await_transform 函数,那么先通过 promise.await_transform(expr) 来对 expr 做一次转换,得到的对象称为 awaitable;否则 awaitable 就是 expr 本身。
    auto operator co_await(int value) {
        struct IntAwaiter {
         int value;
    
         bool await_ready() const noexcept {
           return false;
         }
         void await_suspend(std::coroutine_handle<Generator::promise_type> handle) const {
           handle.promise().value = value;
         }
         void await_resume() {  }
        };
        
        return IntAwaiter{.value = value};
    }
    
    • 接下来使用 awaitable 对象来获取等待体(awaiter)。如果 awaitable 对象有 operator co_await 运算符重载,那么等待体就是 operator co_await(awaitable),否则等待体就是 awaitable 对象本身。
    struct Generator {
    struct promise_type {
     int value;
    
     // 传值的同时要挂起,值存入 value 当中
     std::suspend_always await_transform(int value) {
       this->value = value;
       return {};
     }
    
     ...
    };
    
    std::coroutine_handle<promise_type> handle;
    
    int next() {
     handle.resume();
    
    // 外部调用者或者恢复者可以通过读取 value
     return handle.promise().value;
    }
    };
    
    

    定义了 await_transform 函数之后,co_await expr 就相当于 co_await promise.await_transform(expr) 了。

    • 使用 co_yield: co_yield expr等价于co_await promise.yield_value(expr)
    struct promise_type {
     ...
    
     // 将 await_transform 替换为 yield_value
     std::suspend_always yield_value(int value) {
       this->value = value;
       is_ready = true;
       return {};
     }
     ...
    };
    

定义一个类型 Task 来作为协程的返回值。Task 类型可以用来封装任何返回结果的异步行为

定义以 Task<ResultType> 为返回值类型的协程,并且可以在协程内部使用 co_await 来等待其他 Task 的执行。

  1. 需要一个结果类型来承载正常返回和异常抛出的情况。
  2. 需要为 Task 定义相应的 promise_type 类型来支持 co_returnco_await
  3. Task 实现获取结果的阻塞0函数 get_result 或者用于获取返回值的回调 then 以及用于获取抛出的异常的回调 catching

结果类型的定义:

#include <exception>

template<typename T>
struct Result {
  // 初始化为默认值
  explicit Result() = default;

  // 当 Task 正常返回时用结果初始化 Result
  explicit Result(T &&value) : _value(value) {}

  // 当 Task 抛异常时用异常初始化 Result
  explicit Result(std::exception_ptr &&exception_ptr) : _exception_ptr(exception_ptr) {}

  // 读取结果,有异常则抛出异常
  T get_or_throw() {
    if (_exception_ptr) {
      std::rethrow_exception(_exception_ptr);
    }
    return _value;
  }

 private:
  T _value{};
  std::exception_ptr _exception_ptr;
};

promise_type 的定义:

template<typename ResultType>
struct TaskPromise {
  // 协程立即执行
  std::suspend_never initial_suspend() { return {}; }

  // 执行结束后挂起,等待外部销毁。该逻辑与前面的 Generator 类似
  std::suspend_always final_suspend() noexcept { return {}; }

  // 构造协程的返回值对象 Task
  Task<ResultType> get_return_object() {
    return Task{std::coroutine_handle<TaskPromise>::from_promise(*this)};
  }

  void unhandled_exception() {
    // 将异常存入 result
    result = Result<ResultType>(std::current_exception());
  }

  void return_value(ResultType value) {
    // 将返回值存入 result,对应于协程内部的 'co_return value'
    result = Result<ResultType>(std::move(value));
  }


 private:
  // 使用 std::optional 可以区分协程是否执行完成
  std::optional<Result<ResultType>> result;

};

await_transform:

template<typename ResultType>
struct TaskPromise {
  ...

  // 注意这里的模板参数
  template<typename _ResultType>
  TaskAwaiter<_ResultType> await_transform(Task<_ResultType> &&task) {
    return TaskAwaiter<_ResultType>(std::move(task));
  }
  
  ...
}

TaskAwaiter 的定义:

template<typename R>
struct TaskAwaiter {
  explicit TaskAwaiter(Task<R> &&task) noexcept
      : task(std::move(task)) {}

  TaskAwaiter(TaskAwaiter &&completion) noexcept
      : task(std::exchange(completion.task, {})) {}

  TaskAwaiter(TaskAwaiter &) = delete;

  TaskAwaiter &operator=(TaskAwaiter &) = delete;

  constexpr bool await_ready() const noexcept {
    return false;
  }

  void await_suspend(std::coroutine_handle<> handle) noexcept {
    // 当 task 执行完之后调用 resume
    task.finally([handle]() {
      handle.resume();
    });
  }

  // 协程恢复执行时,被等待的 Task 已经执行完,调用 get_result 来获取结果
  R await_resume() noexcept {
    return task.get_result();
  }

 private:
  Task<R> task;

};

同步阻塞获取结果:

template<typename ResultType>
struct TaskPromise {
  ...

  void unhandled_exception() {
    std::lock_guard lock(completion_lock);
    result = Result<ResultType>(std::current_exception());
    // 通知 get_result 当中的 wait
    completion.notify_all();
  }

  void return_value(ResultType value) {
    std::lock_guard lock(completion_lock);
    result = Result<ResultType>(std::move(value));
    // 通知 get_result 当中的 wait
    completion.notify_all();
  }

  ResultType get_result() {
    // 如果 result 没有值,说明协程还没有运行完,等待值被写入再返回
    std::unique_lock lock(completion_lock);
    if (!result.has_value()) {
      // 等待写入值之后调用 notify_all
      completion.wait(lock);
    }
    // 如果有值,则直接返回(或者抛出异常)
    return result->get_or_throw();
  }

 private:
  std::optional<Result<ResultType>> result;

  std::mutex completion_lock;
  std::condition_variable completion;
}

异步结果回调
异步回调的实现稍微复杂一些,其实主要复杂在对于函数的运用。实际上对于回调的支持,主要就是支持回调的注册和回调的调用。根据结果类型的不同,回调又分为返回值的回调或者抛出异常的回调:

template<typename ResultType>
struct TaskPromise {
  ...

  void unhandled_exception() {
    std::lock_guard lock(completion_lock);
    result = Result<ResultType>(std::current_exception());
    completion.notify_all();
    // 调用回调
    notify_callbacks();
  }

  void return_value(ResultType value) {
    std::lock_guard lock(completion_lock);
    result = Result<ResultType>(std::move(value));
    completion.notify_all();
    // 调用回调
    notify_callbacks();
  }

  void on_completed(std::function<void(Result<ResultType>)> &&func) {
    std::unique_lock lock(completion_lock);
    // 加锁判断 result
    if (result.has_value()) {
      // result 已经有值
      auto value = result.value();
      // 解锁之后再调用 func
      lock.unlock();
      func(value);
    } else {
      // 否则添加回调函数,等待调用
      completion_callbacks.push_back(func);
    }
  }

 private:
  ...

  // 回调列表,我们允许对同一个 Task 添加多个回调
  std::list<std::function<void(Result<ResultType>)>> completion_callbacks;
  
  void notify_callbacks() {
    auto value = result.value();
    for (auto &callback : completion_callbacks) {
      callback(value);
    }
    // 调用完成,清空回调
    completion_callbacks.clear();
  }

}

Task的实现:

template<typename ResultType>
struct Task {

  // 声明 promise_type 为 TaskPromise 类型
  using promise_type = TaskPromise<ResultType>;

  ResultType get_result() {
    return handle.promise().get_result();
  }

  Task &then(std::function<void(ResultType)> &&func) {
    handle.promise().on_completed([func](auto result) {
      try {
        func(result.get_or_throw());
      } catch (std::exception &e) {
        // 忽略异常
      }
    });
    return *this;
  }

  Task &catching(std::function<void(std::exception &)> &&func) {
    handle.promise().on_completed([func](auto result) {
      try {
        // 忽略返回值
        result.get_or_throw();
      } catch (std::exception &e) {
        func(e);
      }
    });
    return *this;
  }

  Task &finally(std::function<void()> &&func) {
    handle.promise().on_completed([func](auto result) { func(); });
    return *this;
  }

  explicit Task(std::coroutine_handle<promise_type> handle) noexcept: handle(handle) {}

  Task(Task &&task) noexcept: handle(std::exchange(task.handle, {})) {}

  Task(Task &) = delete;

  Task &operator=(Task &) = delete;

  ~Task() {
    if (handle) handle.destroy();
  }

 private:
  std::coroutine_handle<promise_type> handle;
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值