C++1z协程线程上下文和协程调度

C++1z coroutine threading context and coroutine scheduling

根据最新的 C++ TS:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/n4628.pdf,基于对 C# async/await 语言支持的理解,我想知道 "execution context" 是什么(从 C# 借用的术语)的 C++ 协程?

我在 Visual C++ 2017 RC 中的简单测试代码表明协程似乎总是在线程池线程上执行,应用程序开发人员几乎没有控制协程可以在哪个线程上下文中执行 - 例如应用程序是否可以强制所有协程(带有编译器生成的状态机代码)仅在主线程上执行,而不涉及任何线程池线程?

在 C# 中,SynchronizationContext 是一种指定 "context" 的方法,其中所有协程 "halves"(编译器生成的状态机代码)将被 post 编辑和执行,如中所示this post: https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/,而当前 Visual C++ 2017 RC 中的协程实现似乎总是依赖并发运行时,它默认在线程池线程上执行生成的状态机代码。是否有类似的同步上下文概念,用户应用程序可以使用它来将协程执行绑定到特定线程?

此外,在 Visual C++ 2017 RC 中实现的协程的当前默认 "scheduler" 行为是什么?即 1) 如何准确指定等待条件?和 2) 当满足等待条件时,谁调用挂起协程的 "bottom half"?

我对 C# 中任务调度的(天真的)推测是 C# "implements" 等待条件纯粹由任务继续 - 等待条件由 TaskCompletionSource 拥有的任务和任何需要等待的代码逻辑合成将被链接为它的延续,因此如果满足等待条件,例如如果从低级网络处理程序接收到完整消息,它会 TaskCompletionSource.SetValue,将底层任务转换为完成状态,有效地允许链式延续逻辑开始执行(将任务置于就绪状态 state/list 来自先前创建的状态) - 在 C++ 协程中,我推测 std::future 和 std::promise 将用作类似的机制(std::future 是任务,而 std::promise 是 TaskCompletionSource,用法也非常相似!)- C++ 协程调度程序(如果有的话)也依赖于某种类似的机制来执行行为吗?

[编辑]:在做了一些进一步的研究之后,我能够编写一个非常简单但非常强大的抽象,称为 awaitable,它支持单线程和协作多任务处理,并具有一个简单的基于 thread_local 的调度程序,它可以在 root 协程启动的线程上执行协程。可以从这个 github 存储库中找到代码:https://github.com/llint/Awaitable

Awaitable 是可组合的,它在嵌套级别维护正确的调用顺序,并且它具有原始生成、定时等待和从其他地方设置就绪的功能,并且可以从中派生出非常复杂的使用模式(例如无限循环协程,只有在某些事件发生时才会被唤醒),编程模型紧密遵循基于 C# 任务的 async/await 模式。请随时提供您的反馈。

相反!

C++协程就是关于控制的。这里的重点是
void await_suspend(std::experimental::coroutine_handle<> handle) 功能。

每个 co_await 都需要等待类型。简单来说,awaitable 类型就是提供这三个功能的类型:

  1. bool await_ready() - 程序应该停止协程的执行吗?
  2. void await_suspend(handle) - 程序会向您传递该协程框架的延续上下文。如果您激活句柄(例如,通过调用句柄提供的 operator () - 当前线程立即恢复协程)。
  3. T await_resume() - 告诉恢复协程的线程在恢复协程时要做什么以及从 co_await.
  4. return 做什么

因此,当您在可等待类型上调用 co_await 时,程序会询问可等待对象是否应暂停协程(如果 await_ready returns false),如果是这样 - 您会得到一个协程句柄,您可以在其中做任何您想做的事情。

例如,您可以将协程句柄传递给线程池。在这种情况下,线程池线程将恢复协程。

您可以将协程句柄传递给一个简单的 std::thread - 您的 自己的 创建线程将恢复协程。

您可以将协程句柄附加到 OVERLAPPED 的派生 class 中,并在异步 IO 完成时恢复协程。

如您所见 - 您可以控制协同程序暂停和恢复的位置和时间 - 通过管理传入 await_suspend 的协同程序句柄。没有 "default scheduler" - 你如何实现等待类型将决定协同程序的调度方式。

那么,VC++ 中会发生什么?不幸的是,std::future 仍然没有 then 函数,因此您 不能 将协程句柄传递给 std::future。如果您在 std::future 上等待 - 程序将只打开一个新线程。看future头给出的源码:

template<class _Ty>
    void await_suspend(future<_Ty>& _Fut,
        experimental::coroutine_handle<> _ResumeCb)
    {   // change to .then when future gets .then
    thread _WaitingThread([&_Fut, _ResumeCb]{
        _Fut.wait();
        _ResumeCb();
    });
    _WaitingThread.detach();
    } 

那么,如果协程以常规 std::thread 启动,为什么您会看到 win32 线程池线程?那是因为它不是协程。 std::async 在幕后调用 concurrency::create_task。 a concurrency::task 默认在 win32 线程池下启动。毕竟, std::async 的全部目的是在另一个线程中启动可调用对象。