在我的C++协程库中的一些地方,我需要遍历一个相互等待的挂起的协程链。
例如,假设Foo0
调用Foo1
,Foo1
调用Foo2
,Foo2
调用FooN
,每个co_await
都是下一个的结果,FooN
当前被挂起,它们的promise类型存储等待者的句柄,以便在它们完成时恢复。因此它们形成了一个从FooN
到Foo0
的单链表。我希望能够迭代该列表,例如,在取消后进行清理,或者为分析提供一个良好的异步堆栈跟踪。
如果所有的协程都有相同的promise类型,只要我有最后一个协程帧的句柄,这就很容易了,我可以像这样实现它:
void WalkCoroutineChain(std::coroutine_handle<Promise> h) {
while (h) {
// ... do something with h ...
Visit(h);
// Move on to the caller of h's coroutine.
h = h.promise().awaiter;
}
}
问题是,实际上它们并不都有相同的承诺类型,它们共享一个共同的承诺类型"模板",甚至共享一个共同的承诺类型基类,但承诺类型本身不同,因为它是根据它们返回的结果类型模板化的。
我的问题是:给定对承诺的引用,是否有任何合法的方式来将其存储为诸如void*
或std::coroutine_handle<>
之类的类型擦除的值,同时保留经由该类型擦除的值执行以下两种操作的能力?
1.恢复或销毁它的协同程序。
1.获取对它的已知基类的引用。(如果有帮助的话,我可以保证没有多重继承。)
这将允许我解决我的问题,因为我可以将awaiter
成员放入一个所有承诺都继承自的公共无模板基类中,并遍历这些承诺的列表。
我很肯定在实践中,这样做可能会奏效:
std::coroutine_handle<> type_erased = GetHandleSomehow();
auto base = std::coroutine_handle<PromiseBase>::from_address(type_erased.address());
但这可能是未定义的行为:
- 在C++20草稿中,coroutine.handle.export.import(https://timsong-cpp.github.io/cppwp/n4868/coroutine.handle.export.import)列出了
std::coroutine_handle<Promise>::from_address
的先决条件"addr
是通过先前对address
的调用获得的",而没有说明任何关于address
的被调用者的类型。 - 在latest draft中,它特别指出"
addr
是通过在类型为 * cv *coroutine_handle<Promise>
的对象上对address
的先前调用获得的",如果Promise
实际上是real promise类型的基类,那么这就不是真的。
2条答案
按热度按时间icnyk63a1#
我认为下面的方法在实践中是可行的,至少在没有多重继承或虚函数的简单情况下(即基类和promise类都是pointer-interconvertible):
PromiseBase::awaiter
可以用作在WalkCoroutineChain
函数中遍历的链接,而不是std::coroutine_handle<Promise<T>>
,因为T
可以在协程链上变化。我读了C20草案和最新的标准草案,都说
get_base_handle
的实现是法律的的:std::coroutine_handle<PromiseBase>::from_promise
的前提条件仅仅是输入引用是“对协程的承诺对象的引用”。在该上下文中,*this
肯定是这样,并且即使当被转换为对PromiseBase
的引用时,大概仍然是这样。我说这“可能在实践中起作用”,是因为我对标准的意图不是100%有信心。也许这意味着要禁止?但如果是这样,肯定不清楚。在缺乏明确措辞的情况下,也值得考虑可实施性。而且我看不出有什么理由不能正确地工作,因为现在clang/libc中已经实现了协程句柄(
void*
指针,指向在已知偏移处具有承诺的协同程序帧);至少在没有多重继承和虚函数的情况下,这似乎是正确的。如果标准的意图是说这是不法律的的,那么我认为措辞可以改进。我已经在标准的GitHub repo上提交了一个issue,要求澄清。
nue99wik2#
我们无法从
std::coroutine_handle<>
那里得到任何形式的承诺,这基本上就是这种专门化的全部意义:与协程交互而不关心它包含什么类型的承诺。但这可能是未定义的行为:
LWG issue 3460。它明确地表明
coroutine_handle<T>::from_address
要求地址是从coroutine_handle<T>
的示例中获得的。这是C++20的一个缺陷,所以它是标准的一部分。