在 C++ 协程中,promise 类型的
final_suspend
成员函数可以返回任意可等待类型,但通常是 std::suspend_always
将销毁协程的责任留给其他人,或者 std::suspend_never
自动销毁协程。后者似乎是一种特殊情况,因为通常不允许恢复处于最终挂起点的协程。但在我的测试中,似乎这种特殊行为并没有统一应用。
问题的关键在于可等待的
await_suspend
成员函数可以具有三种不同返回类型中的任何一种:如果它返回 void
,则协程将保持挂起状态。如果它返回 bool
,则协程将恢复 false
并保持暂停状态 true
。如果它返回 std::coroutine_handle
,则该协程将恢复。
我已经用
final_suspend
的返回值测试了所有这些结果的行为,并且 MSVC、Clang 和 GCC 的所有三个都集中在这种行为上,以实现可等待,其中 await_ready
始终返回 false
:
final_suspend().await_suspend(handle)
返回 void
,则协程将按预期保持在其最终暂停点暂停。final_suspend().await_suspend(handle)
返回 bool(true)
,在这种情况下,协程也会在其最终暂停点保持暂停状态。final_suspend().await_suspend(handle)
返回 bool(false)
,有趣的是,协程会自动销毁,从正在执行的 Promise 类型的析构函数可以看出。这与 std::suspend_never
的行为一致,并表明可以在运行时做出决定。另外值得注意的是,await_resume
也在销毁协程之前被调用,尽管它的结果被丢弃了。final_suspend().await_suspend(handle)
返回另一个协程的句柄,则该协程将按预期恢复,并且当前协程在其最终挂起点保持挂起状态。final_suspend().await_suspend(handle)
返回与给定的句柄相同的句柄,则程序崩溃!结果 1、2 和 4 符合预期。结果 3 和 5 是我最感兴趣的 - 从某个角度来看,你会认为它们会受到相同的对待,因为在这两种情况下,它们都试图“恢复”已完成的协程,但只有结果 3 似乎得到了特殊处理用于自动销毁协程。这使得在返回协程句柄时在运行时决定这一点有点尴尬,因为您必须手动销毁协程,然后返回一个无操作的协程句柄,这比简单地返回
false
更复杂一点相同的效果,甚至可能由于必须返回类型擦除的句柄而使某些用例变得不可能。
我的问题是,C++ 标准中到底在哪里描述或规定了这些具体结果?当我看的时候,我找不到太多。该标准对协程行为的描述显得相当简短。
final_suspend
的可等待的特殊行为似乎很重要,但我找不到它。我可以找到一个声明,final_suspend
及其可等待的使用必须是noexcept
(“不得潜在地抛出”),但就我能找到的特殊规则而言,这就是关于它的。我缺少哪一部分?为什么返回传入的相同句柄会导致与返回 false
不同的行为?
当
await_suspend
返回 false
时,协程恢复。这会导致控制从协程末端流出,从而破坏了协程状态。
当
await_suspend
返回句柄时,会在该句柄上调用 resume()
。这违反了 resume()
的 前提条件,因此行为未定义。