协程的未来 co_await

Future with Coroutines co_await

观看了一次 c++ 讲座 (https://youtu.be/DLLt4anKXKU?t=1589),我试图了解未来如何使用 co_await;示例:

auto compute = []() -> std::future<int> {
int fst = co_await std::async(get_first);
int snd = co_await std::async(get_second);
co_return fst + snd;
};
auto f = compute();
/* some heavy task */ 
f.get();

我无法理解 co_await std::async(get_first) returns 如何以及何时控制 compute。即 std::future 如何实现可等待接口(类型)。

how std::future implements an awaitable interface

就 C++20 而言,它不会。 C++20 提供 co_await 及其附带的语言功能,但不提供任何实际可等待的类型。

std::future如何实现可等待接口与并发 TS std::experimental::future 实现future::then 的方式基本相同。当 future 的值可用时,then 接受一个函数继续执行。 then 的 return 值是一个新的 future<U> (旧的 future<T> 现在变得无效),其中 U 是给定延续的新值函数 returns。新的 future 只有在原始值可用且延续已将其处理为新值时才具有 U 可用。按照这个顺序。

关于 .then 工作原理的确切细节完全取决于 future 的实现方式。这可能取决于特定的 future 是如何 创建的 ,因为来自 std::asyncfuture 具有其他 future 的特殊属性不要。

co_await 只是让这个过程在视觉上更容易理解。 co_awaitable future 只需将协程句柄推入 future::then,从而改变 future.

这里有一个完整的程序,可以使用 C++20 协程等待未来。这几天自己做了学习。

#include <cassert>
#include <coroutine>
#include <future>
#include <iostream>
#include <optional>
#include <thread>

using namespace std::literals;



template <class T>
class FutureAwaitable {
public:
  template <class U> struct BasicPromiseType {
    auto get_return_object() {
      return FutureAwaitable<T>(CoroHandle::from_promise(*this));
    }

    std::suspend_always initial_suspend() noexcept {
      std::cout << "Initial suspend\n";
      return {};
    }

    std::suspend_never final_suspend() noexcept {
      std::cout << "Final suspend\n";
      return {};
    }

    template <class V>
    requires std::is_convertible_v<V, T>
    void return_value(V v) { _value = v; }

    void unhandled_exception() { throw; }

    std::optional<T> _value;
  };

  using promise_type = BasicPromiseType<FutureAwaitable<T>>;
  using CoroHandle = std::coroutine_handle<promise_type>;

  explicit FutureAwaitable(CoroHandle h) : _parent(h) {  }

  ~FutureAwaitable() {
  }


  bool is_ready() const {
      auto & fut = std::get<FutureAwaitable<T> *>(&_parent);
      return fut->wait_for(std::chrono::seconds(0)) != std::future_status::ready;
  }

  FutureAwaitable(std::future<T> && f) {
      _f = &f;
  }

  T get() const { return promise()._value.value(); }

  std::future<T> & std_future() const {
    assert(_f->valid());
    return *_f;
  }

  bool await_ready() {
    if (!(_f->wait_for(std::chrono::seconds(0)) == std::future_status::ready)) {
      std::cout << "Await ready IS ready\n";
      return true;
    }
    else
      std::cout << "Await ready NOT ready\n";
    return false;
  }

  auto await_resume() {
    std::cout << "Await resume" << std::endl;
    return std_future().get();
  }

  bool await_suspend(CoroHandle parent) {
      _parent = parent;
      std::cout << "Await suspend\n";
      return true;
  }

  void resume() {
    assert(_parent);
    _parent.resume();
  }

  auto parent() const { return _parent; }

  bool done() const noexcept {
    return _parent.done();
  }

private:
  auto & promise() const noexcept { return _parent.promise(); }

  CoroHandle _parent = nullptr;
  std::future<T> * _f = nullptr;

};

template <class T> auto operator co_await(std::future<T> &&f) {
  return FutureAwaitable<T>(std::forward<std::future<T>>(f));
}

template <class T> auto operator co_await(std::future<T> & f) {
  return FutureAwaitable<T>(std::forward<std::future<T>>(f));
}


FutureAwaitable<int> coroutine() {
  std::promise<int> p;
  auto fut = p.get_future();
  p.set_value(31);
  std::cout << "Entered func()" << std::endl;
  auto res = co_await std::move(fut);
  std::cout << "Continue func(): " << res << std::endl;

  auto computation = co_await std::async(std::launch::async, [] {
      int j = 0;
      for (int i = 0; i < 1000; ++i) {
          j += i;
      }
      return j;
  });

  auto computation2 = std::async(std::launch::async, [] {
      int j = 0;
      std::this_thread::sleep_for(20s);
      for (int i = 0; i < 1000; ++i) {
          j += i;
      }
      return j;
  });

  auto computation3 = std::async(std::launch::async, [] {
      int j = 0;
      std::this_thread::sleep_for(20s);
      for (int i = 0; i < 1000; ++i) {
          j += i;
      }
      return j;
  });
  co_await computation2;
  co_await computation3;

  std::cout << "Computation result is " << computation << std::endl;
  co_return computation;
}

#define ASYNC_MAIN(coro)                        \
    int main() {                                \
    FutureAwaitable<int> c = coro();                     \
    do { c.resume(); } while (!c.done());                \
    std::cout << "The coroutine returned " << c.get(); \
    return 0; \
}


ASYNC_MAIN(coroutine)