C 1Z Coroutine线程上下文和Coroutine调度

C++1z coroutine threading context and coroutine scheduling

本文关键字:Coroutine 调度 上下文 线程 1Z      更新时间:2023-10-16

根据这一最新C ts:http://www.open-std.org/jtc1/sc222222/wg21/wg21/docs/papers/papers/2016/n4628.pdf,并基于理解在C#async/等待语言支持中,我想知道C Coroutines的"执行上下文"是什么?

我在Visual C 2017 RC中的简单测试代码揭示了Coroutines似乎总是在线程池线程上执行,并且对应用程序开发人员几乎没有控制力可以执行Coroutines的线程上下文,例如。应用程序是否可以迫使所有Coroutines(使用编译器生成的状态机代码)仅在主线程上执行,涉及任何线程池线程?

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

另外,在Visual C 2017 RC中实现的Coroutines的当前默认"调度程序"是什么?即1)如何准确指定等待条件?2)满足等待条件时,谁调用了悬挂式Coroutine的"下半部分"?

我(幼稚的)关于C#任务调度的猜测是C#纯粹是通过任务延续来实现等待条件 - 候补条件是由taskCompletionsource拥有的任务综合的,任何需要等待的代码逻辑都将被予以予以予以的代码逻辑延续,因此,如果满足等待条件,例如如果从低级别网络处理程序收到了一条完整消息,它将执行任务completionsource.setValue,该消息将基础任务转换为已完成的状态,有效地允许链式的持续逻辑开始执行(将任务从准备好以前创建的状态) - 在C Coroutine中,我猜测STD :: Future and STD :: Promise将被用作类似机制(STD :: Future是任务,而STD :: Promise是TaskCompletionsource,并且用法出乎意料的也很相似!

[edit]:在进行了一些进一步的研究之后,我能够编码一个非常简单但非常有力的抽象,称为"正在等待",该抽象支持单线螺纹和合作的多任务处理,并具有一个基于简单的thread_local基于基于three_local的调度程序,可以在线程上执行coroutines启动了 root coroutine。可以从此GitHub回购中找到代码:https://github.com/llint/awaitable

等待以一种方式保持在嵌套级别的正确调用顺序,并且具有原始的屈服,定时等待和从其他地方准备就绪,并且可以从中衍生出非常复杂的使用模式(例如,无限循环只有在某些事件发生时才会被唤醒的Coroutines,编程模型遵循C#的基于C#的基于HASYNC/等待模式。请随时提供反馈。

相反!

c coroutine全都与控制有关。这里的关键是
void await_suspend(std::experimental::coroutine_handle<> handle)功能。

evey co_await期望等待类型。简而言之,等待类型是一种提供这三个功能的类型:

  1. bool await_ready()-程序是否应该阻止执行Coroutine?
  2. void await_suspend(handle)-该程序通过该Coroutine框架的延续上下文。如果您激活句柄(例如,通过调用手柄提供的operator () - 当前线程立即恢复Coroutine)。
  3. T await_resume()-告诉线程在恢复coroutine时恢复该怎么办,以及从 co_await返回的内容。

因此,当您在等待类型的类型上致电co_await时,该程序询问等待的coroutine是否应该暂停(如果await_ready返回false),如果是这样,则您可以在其中做任何可以做的事情。

例如,您可以将Coroutine手柄传递到线池。在这种情况下

您可以将Coroutine手柄传递到简单的std::thread-您的自己创建线程将恢复Coroutine。

您可以将Coroutine手柄连接到派生的OVERLAPPED类中,并在异步IO完成时恢复Coroutine。

您可以看到 - 您可以通过管理await_suspend中传递的Coroutine手柄来控制Coroutine的何时何时暂停并恢复恢复。没有"默认调度程序" - 如何实现您等待类型的类型将决定如何计划。

那么,VC 会发生什么?不幸的是,std::future仍然没有then功能,因此您不能将Coroutine手柄传递给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中启动coroutines,为什么您会看到Win32 ThreadPool-thread?那是因为这不是Coroutine。std::async在幕后打电话给concurrency::create_task。默认情况下,在Win32 ThreadPool下启动了concurrency::task。毕竟,std::async的全部目的是在另一个线程中启动可可。