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.
CodePudding user response:
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.