使用 final_suspend 进行延续的 C++20 协程
C++20 coroutines using final_suspend for continuations
背景
被说服后。我一直在为我的代码库实现协程,并在 final_suspend.
中发现了一个奇怪的地方
上下文
假设您有以下 final_suspend 函数:
final_awaitable final_suspend() noexcept
{
return {};
}
并且,final_awaitable实现如下:
struct final_awaitable
{
bool await_ready() const noexcept
{
return false;
}
default_handle_t await_suspend( promise_handle_t h ) const noexcept
{
return h.promise().continuation();
}
void await_resume() const noexcept {}
};
如果此处的延续是从任务队列 和 中以原子方式检索的,则任务队列可能为空(这可能发生在 await_ready[= 之间的任何时间89=] 和 await_suspend) 然后 await_suspend 必须能够 return 空白继续。
据我了解,当await_suspendreturn一个handle时,returned handle会立即恢复(5.1 in N4775 草稿)。因此,如果此处没有可用的延续,则任何应用程序都会崩溃,因为在从 await_suspend 接收到无效的协程句柄后调用 resume。
执行顺序如下:
final_suspend Constructs final_awaitable.
final_awaitable::await_ready Returns false, triggering await_suspend.
final_awaitable::await_suspend Returns a continuation (or empty continuation).
continuation::resume This could be null if a retrieved from an empty work queue.
似乎没有为有效句柄指定检查(如果 await_suspend returns bool 就是这样)。
问题
- 在这种情况下,您打算如何在没有锁定的情况下将工作队列添加到 await_suspend?寻找可扩展的解决方案。
- 为什么底层协程实现不检查有效句柄。
导致崩溃的人为示例是 here。
解决方案
使用一个虚拟任务,它是 co_yield 的无限循环。这是一种浪费的周期,我宁愿不必这样做,而且我需要为每个执行线程创建单独的虚拟任务句柄,这看起来很愚蠢。
创建 std::coroutine_handle 的专业化,其中 resume 不执行任何操作,returning 该句柄的实例。我不想专门研究标准库。这也不起作用,因为 coroutine_handle<> 没有 done() 和 resume() 为虚拟。
EDIT 1 16/03/2020 调用 continuation() 以原子方式检索延续并将结果存储在 final_awaitable 结构,await_ready 世界 return 如果没有可用的延续则为真。如果有可用的延续 await_ready 将 return false,然后将调用 await_suspend 并且continuation returned(立即恢复)。
这是行不通的,因为任务 returned 的值存储在协程框架中,如果仍然需要该值,则不得破坏协程框架。在这种情况下,它在 await_resume 在 final_awaitable 上被调用后被销毁。
如果任务是连续链中的最后一个,这只是一个问题。
编辑 2 - 2020 年 3 月 20 日 忽略 return 来自 [ 的可用协同例程句柄的可能性=128=]。仅从顶级合作例程恢复继续。这看起来效率不高。
2020/01/04
我还没有找到没有实质性缺点的解决方案。我想我之所以被困在这个问题上是因为 await_suspend 似乎旨在解决这个确切的问题(能够 return a corountine_handle)。我只是无法弄清楚预期的模式。
关于:(实际上只是一个大评论。)
struct final_awaitable
{
bool await_ready() const noexcept
{
return false;
}
bool await_suspend( promise_handle_t h ) const noexcept
{
auto continuation = h.promise().atomically_pop_a_continuation();
if (continuation)
continuation.handle().resume();
return true;//or whatever is meaningfull for your case.
}
void await_resume() const noexcept {}
};
您可以使用 std::noop_coroutine 作为空白延续。
背景
被说服后
上下文
假设您有以下 final_suspend 函数:
final_awaitable final_suspend() noexcept
{
return {};
}
并且,final_awaitable实现如下:
struct final_awaitable
{
bool await_ready() const noexcept
{
return false;
}
default_handle_t await_suspend( promise_handle_t h ) const noexcept
{
return h.promise().continuation();
}
void await_resume() const noexcept {}
};
如果此处的延续是从任务队列 和 中以原子方式检索的,则任务队列可能为空(这可能发生在 await_ready[= 之间的任何时间89=] 和 await_suspend) 然后 await_suspend 必须能够 return 空白继续。
据我了解,当await_suspendreturn一个handle时,returned handle会立即恢复(5.1 in N4775 草稿)。因此,如果此处没有可用的延续,则任何应用程序都会崩溃,因为在从 await_suspend 接收到无效的协程句柄后调用 resume。
执行顺序如下:
final_suspend Constructs final_awaitable.
final_awaitable::await_ready Returns false, triggering await_suspend.
final_awaitable::await_suspend Returns a continuation (or empty continuation).
continuation::resume This could be null if a retrieved from an empty work queue.
似乎没有为有效句柄指定检查(如果 await_suspend returns bool 就是这样)。
问题
- 在这种情况下,您打算如何在没有锁定的情况下将工作队列添加到 await_suspend?寻找可扩展的解决方案。
- 为什么底层协程实现不检查有效句柄。
导致崩溃的人为示例是 here。
解决方案
使用一个虚拟任务,它是 co_yield 的无限循环。这是一种浪费的周期,我宁愿不必这样做,而且我需要为每个执行线程创建单独的虚拟任务句柄,这看起来很愚蠢。
创建 std::coroutine_handle 的专业化,其中 resume 不执行任何操作,returning 该句柄的实例。我不想专门研究标准库。这也不起作用,因为 coroutine_handle<> 没有 done() 和 resume() 为虚拟。
EDIT 1 16/03/2020 调用 continuation() 以原子方式检索延续并将结果存储在 final_awaitable 结构,await_ready 世界 return 如果没有可用的延续则为真。如果有可用的延续 await_ready 将 return false,然后将调用 await_suspend 并且continuation returned(立即恢复)。 这是行不通的,因为任务 returned 的值存储在协程框架中,如果仍然需要该值,则不得破坏协程框架。在这种情况下,它在 await_resume 在 final_awaitable 上被调用后被销毁。 如果任务是连续链中的最后一个,这只是一个问题。
编辑 2 - 2020 年 3 月 20 日 忽略 return 来自 [ 的可用协同例程句柄的可能性=128=]。仅从顶级合作例程恢复继续。这看起来效率不高。
2020/01/04
我还没有找到没有实质性缺点的解决方案。我想我之所以被困在这个问题上是因为 await_suspend 似乎旨在解决这个确切的问题(能够 return a corountine_handle)。我只是无法弄清楚预期的模式。
关于:(实际上只是一个大评论。)
struct final_awaitable
{
bool await_ready() const noexcept
{
return false;
}
bool await_suspend( promise_handle_t h ) const noexcept
{
auto continuation = h.promise().atomically_pop_a_continuation();
if (continuation)
continuation.handle().resume();
return true;//or whatever is meaningfull for your case.
}
void await_resume() const noexcept {}
};
您可以使用 std::noop_coroutine 作为空白延续。