co_await详解(上)

原文链接
和上一篇文章一样,感觉这个文章写得也蛮好的,链接如上。

interface

interface主要分为两种:promiseawaitable。本文将主要讲解promise.

promise

awaiters & awaitable

co_await 必须在context中使用,支持co_await的类型就叫做awaitable类型。
co_await必须再context中使用。
awaitable可以分为两个类型:

  1. Normally Awaitable:promise,没有使用await_transform
  2. Contextually Awaitbale: 使用了await_transform

awaitable类型主要包括三个部分:
3. await_ready
4. await_suspend
5. await_resume

注意区分awaiter和awaitable,下面这个例子是原文里的:

template<typename P, typename T>
decltype(auto) get_awaitable(P& promise, T&& expr)
{
  if constexpr (has_any_await_transform_member_v<P>)
    return promise.await_transform(static_cast<T&&>(expr));
  else
    return static_cast<T&&>(expr);
}

template<typename Awaitable>
decltype(auto) get_awaiter(Awaitable&& awaitable)
{
  if constexpr (has_member_operator_co_await_v<Awaitable>)
    return static_cast<Awaitable&&>(awaitable).operator co_await();
  else if constexpr (has_non_member_operator_co_await_v<Awaitable&&>)
    return operator co_await(static_cast<Awaitable&&>(awaitable));
  else
    return static_cast<Awaitable&&>(awaitable);
}

在完成了封装之后,co_await(<expr>)可以被看做下面这个代码段:

{
  auto&& value = <expr>;
  auto&& awaitable = get_awaitable(promise, static_cast<decltype(value)>(value));
  auto&& awaiter = get_awaiter(static_cast<decltype(awaitable)>(awaitable));
  if (!awaiter.await_ready())
  {
    using handle_t = std::experimental::coroutine_handle<P>;

    using await_suspend_result_t =
      decltype(awaiter.await_suspend(handle_t::from_promise(p)));

    <suspend-coroutine>

    if constexpr (std::is_void_v<await_suspend_result_t>)
    {
      awaiter.await_suspend(handle_t::from_promise(p));
      <return-to-caller-or-resumer>
    }
    else
    {
      static_assert(
         std::is_same_v<await_suspend_result_t, bool>,
         "await_suspend() must return 'void' or 'bool'.");

      if (awaiter.await_suspend(handle_t::from_promise(p)))
      {
        <return-to-caller-or-resumer>
      }
    }

    <resume-point>
  }

  return awaiter.await_resume();
}

await_suspende

分为两种返回值类型:

  1. void返回类型:无条件地转回协程的caller或resumer处
  2. bool返回类型:有条件执行,尤其是在当异步操作被同步完成时,会返回false。也就是说,可以通过bool的返回类型来判断是否实现了异步操作。

await_resume

避免<suspend_coroutine>的损耗,可以实现无需暂停的同步完成。其返回值就是co_await 的结果。

关于异常处理(exception)

在await_resume中,可以抛出异常,并在抛出异常时自动resume,回到协程中去。

Coroutine handle

coroutine handle代表了一个coroutine frame的没有所有权的句柄。
协程句柄类型有以下的接口:

namespace std::experimental
{
  template<typename Promise>
  struct coroutine_handle;

  template<>
  struct coroutine_handle<void>
  {
    bool done() const;

    void resume();
    void destroy();

    void* address() const;
    static coroutine_handle from_address(void* address);
  };

  template<typename Promise>
  struct coroutine_handle : coroutine_handle<void>
  {
    Promise& promise() const;
    static coroutine_handle from_promise(Promise& promise);

    static coroutine_handle from_address(void* address);
  };
}

上述所写到的三个函数如下:

  1. resume,重新激活在resume_point的被暂停的协程。
  2. destroy, 释放coroutine frame所使用的内存。由于RAII,大部分的释放占用内存的工作由操作系统完成,如果不是库的作者的话,尽量不要使用destroy.
  3. promise, 返回一个promise object。
  4. address from_address , 允许句柄和void*的互相转换。这一步主要是为了将context作为参数传递到别的C++的API里。

Synchronisation-free async code

当协程已经挂起时,通过在await_suspend()中启动异步读取操作意味着我们可以在操作完成时恢复协程,而不需要任何线程同步来协调启动操作的线程和完成操作的线程,以下为一个原文中的例子:

Time     Thread 1                           Thread 2
  |      --------                           --------
  |      ....                               Call OS - Wait for I/O event
  |      Call await_ready()                    |
  |      <supend-point>                        |
  |      Call await_suspend(handle)            |
  |        Store handle in operation           |
  V        Start AsyncFileRead ---+            V
                                  +----->   <AsyncFileRead Completion Event>
                                            Load coroutine_handle from operation
                                            Call handle.resume()
                                              <resume-point>
                                              Call to await_resume()
                                              execution continues....
           Call to AsyncFileRead returns
         Call to await_suspend() returns
         <return-to-caller/resumer>

当Thread1到达了suspend_point时,调用await_suspend,并将相关的数据进行存储,存储到coroutine frame中,此时thread1已经挂起,一个新的coroutine frame已经被构造,以供Thread2使用。

Thread2 加载了协程句柄,并进行相关的操作。通过调用句柄的resume()到达resume_point并在达到了该点后,调用await_resume,继续计算直到重新返回Thread1。await_resume的返回值就是co_await操作的返回值。

在获取了await_resume的返回值后,协程会立刻销毁Awaiter对象,之后继续运行。

东西太多了,理解起来也费劲,剩下的一些和内存相关的我将在下一节继续补充说明。

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
下面是一个 `co_await` 的完整示例: ``` #include <iostream> #include <chrono> #include <experimental/coroutine> struct SleepyTask { struct promise_type { SleepyTask get_return_object() { return {}; } std::experimental::suspend_never initial_suspend() { return {}; } std::experimental::suspend_never final_suspend() { return {}; } void return_void() {} }; void operator()(int milliseconds) { std::cout << "Sleeping for " << milliseconds << " milliseconds...\n"; std::this_thread::sleep_for(std::chrono::milliseconds(milliseconds)); std::cout << "Awake!\n"; } }; struct async_example { struct promise_type { async_example get_return_object() { return {}; } std::experimental::suspend_always initial_suspend() { return {}; } std::experimental::suspend_always final_suspend() { return {}; } void return_void() {} }; void operator()() { std::cout << "Starting a long-running task...\n"; co_await SleepyTask{}(1000); std::cout << "The task is complete.\n"; } }; int main() { async_example{}(); std::cout << "Returned from the coroutine.\n"; } ``` 该示例定义了两个结构体:`SleepyTask` 和 `async_example`。`SleepyTask` 是一个模拟任务,它在睡眠指定的毫秒数后输出“唤醒!”。`async_example` 是一个异步示例,它启动了一个长时间运行的任务,并在睡眠 1 秒后输出“任务完成”。 在 `main` 函数中,我们创建了一个 `async_example` 对象并调用它,这将启动一个协程。当协程中的任务完成后,它将输出“返回协程”。 该代码使用 C++ 20 的协程功能,使用 `co_await` 关键字暂停

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值