2017-07-25 100 views
6

我有一个异步函数co_await似乎不是最理想的?

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_handleawait_suspend(h)
(顺便说一句,我不知道如果我能保持到参数参考这里)。

这将是更有效的,如果包装函数可直接获得coroutine_handle作为参数。

这可能是一个隐含参数:

Promise f(coroutine_handle<> h); 
co_await f(); 

或者它可能是一个特殊的关键字参数:

Promise f(coroutine_handle<> h); 
f(co_await); 

我在这里失去了一些东西? (其它的是,额外开销并不大。)

+0

我的图书馆,concurrencpp,真正处理期货和协程精美,非常优化的方式,检查出来:https://github.com/David-Haim/ concurrencpp –

+0

@DavidHaim,我如何与现有的API使用它像'ASIO :: async_read'或'WSARecv'?顺便说一句,你可能想要更新它,'initial_suspend'不能返回布尔值。 – Abyx

+0

你将不得不编写一个将'concurrencpp :: promise'传递给'boost :: async_xxx'并返回'concurencpp :: future'的包装器。那么你使用常规的'co_await'。 –

回答

0

当前的设计有一个重要的未来,co_await采取一般表达式而不是调用表达式。

这使得我们可以这样写代码:

auto f = coro_1(); 
co_await coro_2(); 
co_await f; 

我们可以运行在平行的两个或多个异步任务,然后等待他们两个。

因此,coro_1的实施应在其呼叫中开始其工作,而不是在await_suspend中开始其工作。

这也意味着应该有一个预先分配的内存,其中coro_1将其结果,以及它将需要coroutine_handle

我们可以使用不可复制的Awaitable,并保证复制省略。
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); 
} 
2

由协程TS中定义的“协程”系统被设计为处理异步函数其中:

  1. 返回一个未来状物体(一个对象,表示延迟的返回值)。
  2. 类似将来的对象有能力与延续功能相关联。

async_foo不符合这些要求。它不会返回一个类似未来的对象;它通过延续功能“返回”一个值。而这个延续是作为参数传递的,而不是你用对象的返回类型做的事情。

co_await发生的时候,产生未来的潜在异步过程预计已经开始。或者至少,co_await机器使其可能已启动。

您的建议版本在await_ready功能上失效,这允许co_await处理潜在的异步进程。在未来生成时间和await_ready被调用时,该过程可能已经完成。如果有,则不需要安排恢复协程。因此,它应该发生在这里,在这个线程。

如果轻微堆栈效率低下,那么您将不得不按照Coroutine TS要求的方式执行操作。

处理此问题的一般方法是,coro_foo将直接执行async_foo,并返回一个类似将来的对象,其机制类似于.then。你的问题是,async_foo本身不具有.then样的机制,所以你必须创建一个。

这意味着coro_foo必须通过async_foo函子存储coroutine_handle<>,一个可以由未来的继续机制更新。当然,你还需要同步原语。如果在仿函数执行时句柄已经初始化,那么仿函数调用它,恢复协程。如果函子完成而没有恢复协程,仿函数将设置一个变量让等待机器知道该值已准备就绪。

由于手柄,该变量是的await机械和函子之间的共享,你需要确保两个之间的同步。这是一件相当复杂的事情,但无论它是什么风格的机器都需要。

或者你可以只住了轻微低效率。

+0

'await_ready'用于同步结果(例如缓存的结果或无效的参数错误)。如果一个并行线程完成了'auto e = f();'和'e.await_ready();'之间的异步任务,那么出错了。 – Abyx

+1

@Abyx:“*然后出错了*”它是线程代码; “出错”可能很简单,就像“你的线程意外地睡了一会儿,而另一个过程非常短暂。”功能性线程系统*必须*能够处理所有这些可能性。无法保证快速前进的进展,并且*不能被假设。 'co_await'被设计为能够处理这种情况。 –

+0

许多现有的API使用回调。 (包括每个C API,OS API)。为什么要坚持构建在基于回调的基元之上的未来,以及更多的同步? – Abyx

1

async_foo可以直接从coro_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; 
} 

它看起来并不十分昂贵,
但它并不显著改善问题的代码。
(它也伤害了我的眼睛)

+0

“*它也伤害了我的眼睛*”然后不要看它;)我有点严重。将“未来”的实现集成到某个标头中,并在需要时使用它。 'co_await'的好处在于它使异步代码*显示*同步。当你真正使用'co_await'和'coro_foo'(这是99%的时间),你的代码在异步时会有明显的同步操作逻辑。 –