SynchronizationContext 和 TaskScheduler 之间的概念区别是啥

Posted

技术标签:

【中文标题】SynchronizationContext 和 TaskScheduler 之间的概念区别是啥【英文标题】:What is the conceptual difference between SynchronizationContext and TaskSchedulerSynchronizationContext 和 TaskScheduler 之间的概念区别是什么 【发布时间】:2012-03-23 17:11:59 【问题描述】:

Stephen Toub blogged那个

SynchronizationContext 和 TaskScheduler 都是抽象 代表一个“调度器”,你给它一些工作的东西,它 确定何时何地运行该工作。有很多不同的 调度程序的形式。例如,ThreadPool 是一个调度器:你 调用 ThreadPool.QueueUserWorkItem 以提供要运行的委托,即 委托排队,最终线程池的线程之一 拿起并运行该委托。您的用户界面也有一个 调度程序:消息泵。

所以System.Reactive.Concurrency.EventLoopScheduler、Dispatcher、ThreadPool、TaskScheduler、SyncrhonizationContext 和 IScheduler implementations of Reactive Extensions 在这个意义上都是“调度程序”。

它们有什么区别?

为什么它们都是必要的?我想我得到了 EventLoop、Dispatcher、ThreadPool。 IScheduler 也有很好的解释。 但是我仍然不清楚 TaskScheduler 和 SyncrhonizationContext。

Stephen Cleary's excellent article 解释了SyncrhonizationContext,我想我明白了。那么为什么我们需要TaskScheduler,目前还不清楚。

请解释或指出来源。

【问题讨论】:

答案可能有很多原因。我在这篇 MSDN 博客文章中找到了另一个尚未提及的内容:blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259082.aspx 它说 SyncrhonizationContext.Post 异步方法在执行工作项时不提供通知。文章建议如何添加一个使用 TaskCompletionSource 返回任务的扩展方法。 【参考方案1】:

我刚刚在阅读 Jeffrey Ritcher 的 CLR via C# 书,感谢他,我还可以就该主题给出一些简单的解释。 (假设我不完全同意答案中的全部细节)

首先,TaskScheduler 对象负责执行计划任务。 FCL 附带两种TaskScheduler 派生类型:线程池任务调度程序同步上下文任务调度程序。默认情况下,所有应用程序都使用 线程池任务调度程序。此任务调度程序将任务调度到线程池的工作线程。您可以通过查询TaskScheduler 的静态Default 属性来获取对默认任务调度程序的引用。

同步上下文任务调度程序通常用于具有图形用户界面的应用程序。此任务调度程序将所有任务调度到应用程序的 GUI 线程 这样所有的任务代码都可以成功更新按钮、菜单项等 UI 组件。 同步上下文任务调度器根本不使用线程池。您可以通过查询TaskScheduler的静态FromCurrentSynchronizationContext方法获得对同步上下文任务调度程序的引用。

SynchronizationContextTaskScheduler 实现中可以看出,它在内部使用SynchronizationContext 字段。 FCL 定义了一个基类,称为System.Threading.SynchronizationContext,它解决了所有这些问题:

GUI 应用程序强加一个线程模型,其中线程 创建了一个 UI 元素是唯一允许更新该 UI 的线程 元素。这是一个问题,因为你的代码会抛出异常 如果它尝试通过线程池线程更新 UI 元素。不知何故, 线程池线程必须让 GUI 线程更新 UI 元素。 ASP.NET 应用程序允许任何线程为所欲为。当一个线程池线程开始处理客户端的请求时,它可以 假设客户端的文化,允许 Web 服务器返回 数字、日期和时间的文化特定格式。在 此外,Web 服务器可以假设客户端的身份,因此 服务器只能访问允许客户端访问的资源 使用权。当线程池线程产生异步操作时, 它可能由另一个线程池线程完成,这将是 处理异步操作的结果。虽然这项工作 代表原始客户请求执行, 文化和身份需要“流动”到新的线程池线程,所以 代表客户完成的任何额外工作都是使用 客户的文化和身份信息。

简单地说,SynchronizationContext 派生对象将应用程序模型连接到其线程模型。 FCL 定义了几个派生自 SynchronizationContext,但通常你不会直接处理这些类;事实上,其中许多都没有公开曝光或记录在案。

在大多数情况下,应用程序开发人员不需要了解SynchronizationContext 类的任何信息。当你awaitTask 时,调用线程的SynchronizationContext 得到对象。当一个线程池线程完成TaskSynchronizationContext 使用对象,确保您的应用程序模型具有正确的线程模型。所以,当一个 GUI 线程 awaits Taskawait 运算符后面的代码保证在 GUI 线程上执行为 好吧,允许该代码更新 UI 元素。对于 ASP.NET 应用程序,后面的代码 await 操作符保证在具有客户端文化的线程池线程上执行,并且 与之相关的主要信息

当然,如果您有特殊任务,您可以定义自己的派生自 TaskScheduler 的类 调度需求。微软为任务提供了一堆示例代码,并包含源代码 Parallel Extensions Extras 包中一堆任务调度程序的代码。比如IOTaskSchedulerLimitedConcurrencyLevelTaskSchedulerOrderedTaskSchedulerPrioritizingTaskSchedulerThreadPerTaskScheduler

【讨论】:

【参考方案2】:

每个平台都有自己的“调度程序”,并且围绕它们有自己的抽象。例如WinForms 使用消息泵。 WPF 使用“Dispatcher”中抽象的另一个消息泵。 ThreadPool 是“ThreadPool”中抽象的另一个“调度程序”。这些(以及其他一些)是较低级别的调度程序。

Task 和 TaskScheduler 希望 Task 的用户不必考虑在这些较低级别安排任务(当然,您可以以抽象的方式)。您应该能够开始一项任务,并且环境“调度程序”应该会处理它。例如,TaskFactory.StartNew(()=>LengthyOperation()) 应该可以工作,无论我在什么平台下运行。这就是SynchronizationContext 的用武之地。它知道当前运行的框架中涉及哪些较低级别的调度程序。这被传递给TaskScheduler,并且该调度程序既可以调度任务(可能在线程池上),也可以通过与当前正在运行的框架关联的较低级别的调度程序(请参阅SynchronizationContext)调度延续,以维持同步要求。例如尽管您希望任务在 ThreadPool 中运行,但您可能希望在 UI 线程中继续运行。

重要的是要知道TaskScheduler 是多个其他调度程序的抽象。这不是它存在的唯一原因,而是这种“额外”抽象的原因之一。

【讨论】:

SynchronizationContext 进来了。它知道当前运行的框架中涉及哪些低级调度程序 - 如果它知道,为什么我们需要TaskScheduler?例如: 排队到 WindowsFormsSynchronizationContext 的所有委托一次执行一个;它们由特定的 UI 线程按照它们排队的顺序执行。 Source 如果SynchronizationContext 可以安排任务执行,那么我仍然看不到TaskScheduler 的需求。跨度> 传递给TaskScheduler - 什么传递给TaskScheduler? TaskScheduler 是抽象的,所以从技术上讲,没有任何东西传递给它。 TaskScheduler 的实现将接受传递给它们的信息。他们需要发挥什么作用取决于每个实现。对于现在 Fx 中的两个 TaskScheduler 实现,您不必自己创建它们,而是接受其中一个的单例。一般来说,您不关心传递给这些的内容。不过,也许我错过了您的问题的重点;如果是这样,你能澄清一下吗? 你能给我举一个although you'd like your Task to run in the ThreadPool, you may want a continuation to run in the UI thread.的例子吗? @variable 您需要对 UI 执行的任何操作(例如更改控件值或更改可见内容)然后您需要在 UI 线程上执行此操作。【参考方案3】:

虽然如引用的那样,

SynchronizationContext 和 TaskScheduler 都是抽象 代表一个“调度器”

IMO,抽象程度(以及 API)不同。 SynchronizationContext 是一个更通用的 API,因为 Post/Send 采用简单的方法委托。

另一方面,TaskScheduler 是一种特定于 TPL 的抽象——因此它提供了诸如 QueueTask 之类的方法来处理Task 对象。使用同步上下文而不是任务调度器(即拥有 SynchronizationContext 的特定于 TPL 的实现)会使使用任务调度变得更加乏味(当然,这将是 TPL 上下文中的弱类型 API)。因此,TPL 设计人员选择了对 TPL 有意义的抽象调度程序 API 建模(这就是抽象的目的 - 对吗?) - 当然,为了弥补差距,FCL 包含一个内部类SynchronizationContextTaskScheduler,它是包装器 TaskScheduler 实现同步上下文。

SynchronizationContext 是在 .NET 2.0 中引入的,而 TPL 是在 .NET 4 中引入的。有趣的是,如果顺序相反,FCL 设计人员会选择什么,即如果 TPL 在 .网 2.0。 IMO,通过将委托建模为特定专业化的任务,可以使用 TaskScheduler 代替 SynchrinizationContext。

【讨论】:

谢谢维奈。我还假设一个比另一个稍新。仍然让我感到困惑的是,每个异步库似乎都坚持设计自己的抽象,即使它们属于同一代。看看 Rx(Reactive Extensions),它也是 NET 4。他们得到了 IScheduler 和一些实现。与任务无关。而这一次它不是一个抽象类,而是一个接口。鉴于图书馆的多样性,每个图书馆都有自己的优势,但又有着巨大的重叠,我希望能有一本书,或者一篇文章来整理它们。 @MichaelKariv,AFAIK,Rx 不是 FCL 的一部分 - 它是在 .NET 4 之后开发和发布的库。它可能会或可能不会添加到 Fx 的下一个版本。话虽如此,我相信您将继续看到同一概念的多个抽象,因为一个抽象可能无法适合所有场景(不是从功能角度而是从界面角度进行适配) SynchronizationContext 更多的是关于执行亲和性的要求。例如,如果要更新控件,则需要在 UI 线程上进行。该要求在 SynchronizationContext 中进行了抽象。在 TaskScheduler 中抽象了单个任务如何相互关联以及在何处运行(可能需要同步)。相关但正交的抽象。 @PeterRitchie:所以,SynchroContext 是关于“哪里”,而调度程序是关于“哪里”和“什么顺序”。因此,它们不是正交的。如果调度程序只是关于“什么顺序”,它们将是真正正交的。事实上,它们只是重叠。我个人喜欢将 SynCtx 视为实现(任务)调度程序的“低级”“特定于案例”的构建块。但不是正交的!

以上是关于SynchronizationContext 和 TaskScheduler 之间的概念区别是啥的主要内容,如果未能解决你的问题,请参考以下文章

SynchronizationContext 有啥作用?

寻找自定义 SynchronizationContext 的示例(单元测试所需)

SynchronizationContext是什么?

SynchronizationContext笔记

SynchronizationContext(同步上下文)综述

SynchronizationContext.CreateCopy 的目的