使用 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 就是这样)。

问题

  1. 在这种情况下,您打算如何在没有锁定的情况下将工作队列添加到 await_suspend?寻找可扩展的解决方案。
  2. 为什么底层协程实现不检查有效句​​柄。

导致崩溃的人为示例是 here

解决方案

  1. 使用一个虚拟任务,它是 co_yield 的无限循环。这是一种浪费的周期,我宁愿不必这样做,而且我需要为每个执行线程创建单独的虚拟任务句柄,这看起来很愚蠢。

  2. 创建 std::coroutine_handle 的专业化,其中 resume 不执行任何操作,returning 该句柄的实例。我不想专门研究标准库。这也不起作用,因为 coroutine_handle<> 没有 done()resume() 为虚拟。

  3. EDIT 1 16/03/2020 调用 continuation() 以原子方式检索延续并将结果存储在 final_awaitable 结构,await_ready 世界 return 如果没有可用的延续则为真。如果有可用的延续 await_ready 将 return false,然后将调用 await_suspend 并且continuation returned(立即恢复)。 这是行不通的,因为任务 returned 的值存储在协程框架中,如果仍然需要该值,则不得破坏协程框架。在这种情况下,它在 await_resumefinal_awaitable 上被调用后被销毁。 如果任务是连续链中的最后一个,这只是一个问题。

  4. 编辑 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 作为空白延续。