简体   繁体   中英

What is the best way to return value from promise_type

I'm a bit stuck with coroutines in C++20. My attempt to create an asynchronous task:


template <typename T>
struct Task {
 public:
  struct promise_type {
    using Handle = std::coroutine_handle<promise_type>;

    promise_type() = default;
    ~promise_type() = default;

    Task get_return_object() { return Task{Handle::from_promise(*this)}; }

    std::suspend_never initial_suspend() { return {}; }
    std::suspend_never final_suspend() noexcept { return {}; }

    void return_value(T val) noexcept { value_ = val; }
    void unhandled_exception() { std::cout << "Unhandled exception in coroutine\n"; }
    
    T value_;
  };

  explicit Task(typename promise_type::Handle coro) : coro_(coro) {}

  std::optional<T> Get() { 
    return coro_.done() ? 
      std::make_optional(coro_.promise().value_) : std::nullopt; 
  }

 private:
  typename promise_type::Handle coro_;
};

Dummy usage example:

Task<int> Coro() {
  co_await std::suspend_never();
  co_return 1;
}

int main() {
  auto task = Coro();
  std::cout << *task.Get();
  return 0;
}

The problem is in Task::Get() method. When I'm trying to get a value from promise_type it is already destroyed. I'm trying to connect Task and promise_type with a pointer, but it looks a bit ugly. I believe there is some standard and simple approach for this.

UPDATE:

This happens only if the coroutine is not suspended. In this case the promise_type is destroyed before Get() call.

I'm just learning coroutines myself, but I believe that the way to fix this is to change how the Task is destroyed.

At the moment, you have final_suspend return std::suspend_never . So control returns immediately to the caller ( main ) and the promise is destroyed. In that case, you have to do something like this:

I'm trying to connect Task and promise_type with a pointer, but it looks a bit ugly

Basically so that the promise can write its return_value into the Task .


But we could do it a little bit differently. You could always suspend:

std::suspend_always final_suspend() noexcept { return {}; }

And ensure that the Task itself destroys the coroutine:

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

This way, the promise isn't destroyed until task is destroyed, which means that task.Get() isn't accessing a dangling... coroutine. You can see the difference here .

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