18

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

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

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

此外,在 Visual C++ 2017 RC 中实现的协程的当前默认“调度程序”行为是什么?即 1) 如何准确指定等待条件?2)当等待条件满足时,谁调用挂起协程的“下半部分”?

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

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

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

4

1 回答 1

17

反之!

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

每个co_await人都期望等待类型。简而言之,可等待类型是提供以下三个功能的类型:

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

因此,当您调用co_await可等待类型时,程序会询问可等待协程是否应该暂停(如果await_ready返回 false),如果是,您将获得一个协程句柄,您可以在其中做任何您喜欢的事情。

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

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

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

正如您所看到的 - 您可以通过管理传入的协程句柄来控制协程暂停和恢复的位置和时间await_suspend。没有“默认调度程序” - 您如何实现等待类型将决定如何调度协程。

那么,在 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();
    } 

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

于 2017-01-01T07:57:59.043 回答