C++20协程:实现可期待的未来
C++20 coroutines: implementing an awaitable future
自从协程 TS 在 Kona 的 ISO 会议上被接受到 C++20 中后,我开始为自己尝试一下它们。 Clang 已经对协同程序提供了不错的支持,但仍然缺乏库支持的实现。特别是Awaitable类型std::future
、std::generator
等还没有实现
因此,我决定让 std::future
变得可等待。我基本上遵循了 talk by James McNellis at CppCon 2016,特别是这张幻灯片:
现在是 2019 年,我实际上在使用这张幻灯片上的代码(大概未经测试?)时遇到了一些问题:
- 在我看来,重载
operator co_await
已经不是问题了?相反,应该使用 promise_type
的可选 await_transform
。不过不确定我是否做对了。
- future 的
then
continuation 按值捕获句柄,但resume
成员函数不是const-qualified。我通过制作 lambda mutable
. 来解决这个问题
此外,then
和 is_ready
在 std::future
中不可用,但属于 std::experimental::future
which is still missing from my libc++ version. To avoid dealing with the Awaiter and to implement future continuations, I wrote a derived future class which is Awaitable and an Awaiter. It is my understanding that eventually both would also be true of std::future
. You can see my example on Compiler Explorer 的一部分。它确实编译。
但是,它也会段错误。当 get()
被调用时,这发生在 await_resume
中。这实际上并不奇怪,因为那时 valid()
returns false
(调用 get()
UB)。我认为这是因为当then
用于继续未来时,原来的未来object被移动到异步未来,从而使旧的未来(*this
在[=24时=] 被称为,所以在移动之后)。我对 then
的实现大致上受到 this answer and this code I found on GitHub. Those may not be ideal, but cppreference explicitly states valid() == false
的启发,作为调用 then
的后置条件,因此我认为移出原始 future 是正确的。
我在这里错过了什么? "bug" 似乎已经出现在上面的幻灯片中。我怎样才能调和这个问题?有谁知道 Awaitable 未来的(工作)现有实施?谢谢。
正如您自己提到的,问题是因为 future
在调用 .then()
后移动了。诀窍是在准备就绪后将其移回。如果传递给 .then()
的延续采用 future
,而不是它持有的值,则可以完成此操作。
以下是我从您的代码中提取并更改的功能。我还将它们从将东西作为参数传递给 std::async
更改为仅捕获它们,因为这对我来说看起来更直观,但这不是这里的重要变化。
template <typename Work>
auto then(Work&& w) -> co_future<decltype(w())> {
return { std::async([fut = std::move(*this), w = std::forward<Work>(w)]() mutable {
fut.wait();
return w();
})};
}
template <typename Work>
auto then(Work&& w) -> co_future<decltype(w(std::move(*this)))> {
return { std::async([fut = std::move(*this), w = std::forward<Work>(w)]() mutable {
return w(std::move(fut));
})};
}
void await_suspend(std::experimental::coroutine_handle<> ch) {
then([ch, this](auto fut) mutable {
*this = std::move(fut);
ch.resume();
});
}
顺便说一句,VS2017 抱怨在承诺类型中同时具有 set_exception()
和 unhandled_exception()
。我删除了 set_exception()
并将 unhandled_exception()
更改为:
void unhandled_exception() {
_promise.set_exception(std::current_exception());
}
自从协程 TS 在 Kona 的 ISO 会议上被接受到 C++20 中后,我开始为自己尝试一下它们。 Clang 已经对协同程序提供了不错的支持,但仍然缺乏库支持的实现。特别是Awaitable类型std::future
、std::generator
等还没有实现
因此,我决定让 std::future
变得可等待。我基本上遵循了 talk by James McNellis at CppCon 2016,特别是这张幻灯片:
现在是 2019 年,我实际上在使用这张幻灯片上的代码(大概未经测试?)时遇到了一些问题:
- 在我看来,重载
operator co_await
已经不是问题了?相反,应该使用promise_type
的可选await_transform
。不过不确定我是否做对了。 - future 的
then
continuation 按值捕获句柄,但resume
成员函数不是const-qualified。我通过制作 lambdamutable
. 来解决这个问题
此外,then
和 is_ready
在 std::future
中不可用,但属于 std::experimental::future
which is still missing from my libc++ version. To avoid dealing with the Awaiter and to implement future continuations, I wrote a derived future class which is Awaitable and an Awaiter. It is my understanding that eventually both would also be true of std::future
. You can see my example on Compiler Explorer 的一部分。它确实编译。
但是,它也会段错误。当 get()
被调用时,这发生在 await_resume
中。这实际上并不奇怪,因为那时 valid()
returns false
(调用 get()
UB)。我认为这是因为当then
用于继续未来时,原来的未来object被移动到异步未来,从而使旧的未来(*this
在[=24时=] 被称为,所以在移动之后)。我对 then
的实现大致上受到 this answer and this code I found on GitHub. Those may not be ideal, but cppreference explicitly states valid() == false
的启发,作为调用 then
的后置条件,因此我认为移出原始 future 是正确的。
我在这里错过了什么? "bug" 似乎已经出现在上面的幻灯片中。我怎样才能调和这个问题?有谁知道 Awaitable 未来的(工作)现有实施?谢谢。
正如您自己提到的,问题是因为 future
在调用 .then()
后移动了。诀窍是在准备就绪后将其移回。如果传递给 .then()
的延续采用 future
,而不是它持有的值,则可以完成此操作。
以下是我从您的代码中提取并更改的功能。我还将它们从将东西作为参数传递给 std::async
更改为仅捕获它们,因为这对我来说看起来更直观,但这不是这里的重要变化。
template <typename Work>
auto then(Work&& w) -> co_future<decltype(w())> {
return { std::async([fut = std::move(*this), w = std::forward<Work>(w)]() mutable {
fut.wait();
return w();
})};
}
template <typename Work>
auto then(Work&& w) -> co_future<decltype(w(std::move(*this)))> {
return { std::async([fut = std::move(*this), w = std::forward<Work>(w)]() mutable {
return w(std::move(fut));
})};
}
void await_suspend(std::experimental::coroutine_handle<> ch) {
then([ch, this](auto fut) mutable {
*this = std::move(fut);
ch.resume();
});
}
顺便说一句,VS2017 抱怨在承诺类型中同时具有 set_exception()
和 unhandled_exception()
。我删除了 set_exception()
并将 unhandled_exception()
更改为:
void unhandled_exception() {
_promise.set_exception(std::current_exception());
}