如何以最简单的方式C++使用co_await运算符?

How to use co_await operator in C++ the simpliest way?

提问人:Nikitf777 提问时间:11/10/2023 最后编辑:Nikitf777 更新时间:11/10/2023 访问量:45

问:

我应该做的最低限度的操作集是什么来使用 C++ 运算符(如 C# 运算符)?co_awaitawait

有一篇关于 cvotestense 的文章,其中有类似 C# 的类用作返回类型,但我在标准库中找不到它。当函数返回 void 时,这可预见地会导致编译错误。task<>

C 异步 async-await ++20 c ++-coroutine

评论

0赞 Nicol Bolas 11/10/2023
C++ 标准库不包含任何表示协程的实质性类型。也就是说,没有等价于 .因此,虽然语言支持它们,但您必须自己建造机器;这是您需要的“最低限度的一组操作”。task
0赞 Raymond Chen 11/10/2023
与具有语言定义的协程实现 (Task) 的 C# 不同,C++ 是一个构建块,您可以在其上构建协程。很多人都写过协程库,每个库都有不同的特点。有些使用热启动,有些使用延迟启动。有些支持COM线程,有些则与线程无关。与具有观点(热启动,COM 支持)的 C# 不同,C++ 将其留给每个库来决定其策略是什么。co_awaitco_await

答:

0赞 John Leidegren 12/10/2023 #1

task在这种情况下,只是您自己组装的管道的占位符。

从 https://en.cppreference.com/w/cpp/language/coroutines

每个协程都必须具有满足许多要求的返回类型

当你在代码中使用时,你需要.编译器将对函数执行操作。例:co_*#include <coroutine>

void csp_await() {
  bidirectional_channel<int> ch;

  co_await ch.send(1);
}

上面的代码会导致以下错误:

类“std::coroutine_traits”没有成员“promise_type”

无论编译器在做什么,它都在寻找一个,但它找不到它。promise_type

为了解决这个问题,我们需要引入以下代码。

struct task_handle {};

struct task : std::coroutine_handle<task_handle> {
  using promise_type = task_handle;
};

函数的返回类型更改为使用我们的类型,这会导致不同的错误:csp_awaittask

“task::p romise_type”没有成员“initial_suspend”

因此,我们添加了更多内容

struct task_handle;

struct task : std::coroutine_handle<task_handle> {
  using promise_type = task_handle;
};

struct task_handle {
  task                get_return_object() { return {task::from_promise(*this)}; }
  std::suspend_always initial_suspend() noexcept { return {}; }
  std::suspend_always final_suspend() noexcept { return {}; }
  void                return_void() {}
  void                unhandled_exception() {}
};

接下来要做的是使协程的功能友好。它目前是一个具有返回类型的阻塞函数,不能按原样使用。sendbidirectional_channelvoid

template <typename T> struct send_async_awaiter {
  bidirectional_channel<int> &ch_;
  T                           val_;

  bool await_ready() const noexcept { return false; }
  void await_suspend(std::coroutine_handle<> handle) {}
  void await_resume() noexcept {}
};

template <typename T> send_async_awaiter<T> send_async(bidirectional_channel<T> &ch, const T &val) {
  return send_async_awaiter{ch, val};
}

send_async_awaiter是我们可等待的、协程友好的类型。 正是函数需要更改为可等待的内容。此代码可编译,但不执行任何操作。send_asyncsend

我不想对 CSP 进行此介绍,但我需要详细介绍通道发送函数中需要发生的情况。在此示例中,我使用了一个无缓冲通道,该通道应暂停,直到有现成的接收器。假设接收方还没有准备好开始,我们需要将发送操作停放在某个地方。为此,我们只需要考虑 和 . 返回 false,因此接下来要发生的事情将是 。我们得到了一个协程的通用句柄,我们可以用它来停放它。await_readyawait_suspendawait_readyawait_suspend

我开始的实现是基于线程的。它使用互斥锁和条件变量来唤醒阻塞的线程,但我认为类型不应该以相同的方式实现。相反,我将考虑一种侵入性方法,将悬挂的协程停放在通道内。我认为这是有道理的,因为这是我以后需要能够找到一个停放的协程,以便在接收器准备就绪时恢复。这也意味着我们可以编写一个完全单线程的 CSP 库,该库使用协作式而不是抢占式多线程。我认为向这个异步CSP库添加并行性比采用另一种方式更容易。bidirectional_channelbidirectional_channel_async

我没有在这里详细介绍所有内容,但这是一个开始。我很高兴它像这样解耦,因为你可以在此基础上构建任何你认为最好的东西。