简体   繁体   中英

Future with Coroutines co_await

Watching a c++ lecture ( https://youtu.be/DLLt4anKXKU?t=1589 ), I tried to understand how future work with co_await; example:

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();

I can't understand how and when co_await std::async(get_first) returns control to compute . ie how std::future implements an awaitable interface (type).

how std::future implements an awaitable interface

Well as far as C++20 is concerned, it doesn't . C++20 provides co_await and its attendant language functionality, but it doesn't provide any actual awaitable types.

How std::future could implement the awaitable interface is basically the same as how std::experimental::future from the Concurrency TS implements future::then . then takes a function to be continued when the future 's value becomes available. The return value of then is a new future<U> (the old future<T> now becomes non-functional), where U is the new value that the given continuation function returns. That new future will only have a U available when the original value is available and when the continuation has processed it into the new value. In that order.

The exact details about how .then works depend entirely on how future is implemented. And it may depend on how the specific future was created , as future s from std::async have special properties that other future s don't.

co_await just makes this process much more digestible visually. A co_await able future would simply shove the coroutine handle into future::then , thereby altering the future .

Here there is a full program that can await futures with C++20 coroutines. I did it myself these days to learn.

#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)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM