co_await似乎不是最佳的?
co_await appears to be suboptimal?
>我有一个异步函数
void async_foo(A& a, B& b, C&c, function<void(X&, Y&)> callback);
我想在无堆栈协程中使用它,所以我写
auto coro_foo(A& a, B& b, C& c, X& x) /* -> Y */ {
struct Awaitable {
bool await_ready() const noexcept { return false; }
bool await_suspend(coroutine_handle<> h) {
async_foo(*a_, *b_, *c_, [this, h](X& x, Y& y){
*x_ = std::move(x);
y_ = std::move(y);
h.resume();
});
}
Y await_resume() {
return std::move(y);
}
A* a_; B* b_; C* c_; X* x_; Y y_;
};
return Awaitable{&a, &b, &c, &x};
}
那么我可以这样使用它:
Y y = co_await coro_foo(a, b, c, x);
编译器会将其重写为:
auto e = coro_foo(a, b, c, x);
if (!e.await_ready()) {
<suspend>
if (e.await_suspend(h)) return;
resume-point:
<resume>
}
Y y = e.await_resume();
有了这个,协程将在挂起时保持a_
、b_
和c_
,当它只需要保留它们直到我们coroutine_handle
await_suspend(h)
。
(顺便说一句,我不确定我是否可以在这里保留对参数的引用。
如果包装函数可以直接coroutine_handle
作为参数,效率会高得多。
这可能是一个隐含的论点:
Promise f(coroutine_handle<> h);
co_await f();
或者它可能是一个特殊的关键字参数:
Promise f(coroutine_handle<> h);
f(co_await);
我在这里错过了什么吗?(其他开销不是那么大。
协程 TS 定义的"协程"系统旨在处理异步函数,这些函数:
返回- 一个类似未来的对象(表示延迟返回值的对象(。
- 类未来对象具有与延续函数相关联的能力。
async_foo
不符合这些要求。它不会返回类似未来的对象;它通过延续函数"返回"一个值。并且此延续作为参数传递,而不是对对象的返回类型执行的操作。
当co_await
发生时,生成未来的潜在异步过程预计已经开始。或者至少,co_await
机器使它有可能启动。
您建议的版本会失去await_ready
功能,该功能允许co_await
处理潜在的异步进程。在生成未来和调用await_ready
之间,该过程可能已经完成。如果有,则无需计划协程的恢复。因此,它应该就在这里,在这个线程上发生。
如果堆栈效率低下困扰您,那么您将不得不按照协程 TS 希望您的方式做事。
处理此问题的一般方法是coro_foo
直接执行async_foo
并返回具有类似.then
机制的类似未来的对象。你的问题是async_foo
本身没有类似.then
的机制,所以你必须创建一个。
这意味着coro_foo
必须async_foo
存储coroutine_handle<>
的函子传递,该函子可以通过未来的延续机制进行更新。当然,您还需要同步基元。如果在执行函子时句柄已初始化,则函子将调用它,恢复协程。如果函子在没有恢复协程的情况下完成,则函子将设置一个变量,让 await 机器知道值已准备就绪。
由于句柄和此变量在 await 机器和函子之间共享,因此您需要确保两者之间的同步。这是一件相当复杂的事情,但这是.then
式机器所需要的。
或者你可以忍受轻微的低效率。
当前的设计有一个重要的未来,co_await
采用一般表达式而不是调用表达式。
这允许我们编写如下代码:
auto f = coro_1();
co_await coro_2();
co_await f;
我们可以并行运行两个或多个异步任务,然后等待它们。
因此,coro_1
的实施应该在其呼吁中开始工作,而不是在await_suspend
中开始。
这也意味着应该有一个预先分配的内存,coro_1
将在其中放置其结果,并将coroutine_handle
放在何处。
我们可以使用不可复制的Awaitable
和保证的复制 elision.async_foo
将从Awaitable
的构造函数调用:
auto coro_foo(A& a, B& b, C& c, X& x) /* -> Y */ {
struct Awaitable {
Awaitable(A& a, B& b, C& c, X& x) : x_(x) {
async_foo(a, b, c, [this](X& x, Y& y){
*x_ = std::move(x);
y_ = &y;
if (done_.exchange(true)) {
h.resume(); // Coroutine resumes inside of resume()
}
});
}
bool await_ready() const noexcept {
return done_;
}
bool await_suspend(coroutine_handle<> h) {
h_ = h;
return !done_.exchange(true);
}
Y await_resume() {
return std::move(*y_);
}
atomic<bool> done_;
coroutine_handle<> h_;
X* x_;
Y* y_;
};
return Awaitable(a, b, c, &x);
}
如果我们使用类似未来的类,可以直接从coro_foo
调用async_foo
。
这将花费我们一个分配和一个原子变量:
static char done = 0;
template<typename T>
struct Future {
T t_;
std::atomic<void*> addr_;
template<typename X>
void SetResult(X&& r) {
t_ = std::move(r);
void* h = addr_.exchange(&done);
if (h) std::experimental::coroutine_handle<>::from_address(h).resume();
}
bool await_ready() const noexcept { return false; }
bool await_suspend(std::experimental::coroutine_handle<> h) noexcept {
return addr_.exchange(h.address()) != &done;
}
auto await_resume() noexcept {
auto t = std::move(t_);
delete this; // unsafe, will be leaked on h.destroy()
return t;
}
};
Future<Y>& coro_foo(A& a, B& b, C& c, X& x) {
auto* p = new Future<Y>;
async_foo(a, b, c, [p, &x](X& x_, Y& y_) {
x = std::move(x_);
p->SetResult(y_);
});
return *p;
}
它看起来不是很贵,
但它并没有显着改善问题中的代码。
(也疼我的眼睛(
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 在c代码之间共享数据的最佳方式
- 使用std::source_location报告错误的最佳实践
- 派生类销毁的最佳实践是什么
- 将寄存器设计成可由C和C++访问的外设的最佳实践
- 在两台机器之间进行时间戳的最佳c++chrono函数是什么
- 使用QQuickFramebufferObject时同步数据的最佳方式是什么
- 在C++中向零方向近似的最佳方法
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 从嵌套在std::映射中的std::列表中删除元素的最佳方式
- 如果条件为TRUE(最佳方式?),则在do while循环中后置增量
- 检测win32服务创建和删除的最佳方法
- 在reactor中存储eventHandlers的最佳方式是什么
- 在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?
- 在 c++ 中对类中的 c 字符串动态数组进行排序的最佳方法是什么?
- 在AVX通道中混洗的最佳方式
- 程序顶部的声明与定义(最佳实践)
- 别名模板的专业化 C++11 中没有开销的最佳替代方案