Stephen Toub在博客中写道
SynchronizationContext和TaskScheduler都是抽象代表一个"调度器",你给它一些工作,它决定何时何地运行该工作。有很多不同的调度程序的形式。例如,ThreadPool是一个调度器:调用ThreadPool.QueueUserWorkItem以提供要运行的委托委托进入队列,线程池的一个线程最终拿起并运行该代理。您的用户界面也有scheduler:消息泵。
因此System.Reactive.Concurrency.EventLoopScheduler
、Dispatcher、ThreadPool、TaskScheduler、SyncrhonizationContext和IScheduler实现的Reactive Extensions都是";调度器";从这个意义上说。
它们之间有什么区别?
为什么它们都是必要的?我想我得到了EventLoop、Dispatcher、ThreadPool。IScheduler也得到了很好的解释
但是TaskScheduler和SyncrhonizationContext对我来说仍然不清楚
Stephen Cleary的优秀文章解释了SyncrhonizationContext,我想我明白了。为什么我们需要TaskScheduler,还不清楚。
请解释或指出来源。
我刚刚在读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
类的任何信息。当您await
是Task
时,调用线程的SynchronizationContext
获得对象。当线程池线程完成Task
时,SynchronizationContext
对象,确保为您的应用程序模型提供正确的线程模型。因此,当GUI线程awaits
是Task
,await
运算符后面的代码保证在GUI线程上执行为well,允许该代码更新UI元素。对于ASP.NET应用程序,wait运算符保证在具有客户端区域性和与其相关的主要信息。
当然,如果您有特殊任务,您可以定义自己的从TaskScheduler
派生的类调度需求。Microsoft为任务提供了一组示例代码,其中包括Parallel Extensions Extras包中的一组任务调度器的代码。如IOTaskScheduler
、LimitedConcurrencyLevelTaskScheduler
、OrderedTaskScheduler
、PrioritizingTaskScheduler
、ThreadPerTaskScheduler
。
每个平台都有自己的"调度器",它们有自己的抽象。例如,WinForms使用消息泵。WPF使用"Dispatcher"中抽象的另一个消息泵。ThreadPool是"ThreadPool"中抽象的另一个"调度器"。这些(以及其他一些)是较低级别的调度器。
Task和TaskScheduler希望Task的用户不必考虑在这些较低级别上调度任务(当然,您可以以抽象的方式)。你应该能够启动一个任务,环境"调度器"应该负责它。例如,无论我在什么平台上运行,TaskFactory.StartNew(()=>{LengthyOperation()})
都应该工作。这就是SynchronizationContext
的作用所在。它知道当前运行的框架中涉及哪些较低级别的调度器。它被传递到TaskScheduler
,并且该调度器既可以调度任务(可能在线程池上),也可以通过与当前运行的框架相关联的较低级别调度器来调度继续(参见SynchronizationContext
),以保持同步要求。例如,尽管您希望Task在ThreadPool中运行,但您可能希望在UI线程中运行continuation。
重要的是要知道TaskScheduler
是多个其他调度器的抽象。这不是它存在的唯一原因,也是这种"额外"抽象的原因之一
SynchronizationContext和TaskScheduler都是抽象代表"调度器"
IMO,抽象程度(因此API)不同。从某种意义上说,SynchronizationContext是一个更通用的API,Post/Send采用一个简单的方法委托。
另一方面,TaskScheduler是TPL特有的抽象,因此它提供了QueueTask等处理Task
对象的方法。使用同步上下文而不是任务调度程序(即具有特定于TPL的SynchronizationContext实现)会使处理任务调度变得更加乏味(当然,在TPL的上下文中,它将是弱类型的API)。因此,TPL设计人员选择为抽象调度器API建模,这对TPL来说是有意义的(这就是抽象的目的,对吧?)-当然,为了弥补这一差距,FCL包含一个内部类SynchronizationContextTaskScheduler
,它是SynchronizationContext上的包装任务调度器实现。
SynchronizationContext是在.NET 2.0中引入的,而TPL是在.NET 4中引入的。有趣的是,如果顺序相反,FCL设计者会选择什么,也就是说,如果TPL在.NET 2.0时代就已经存在了,会发生什么。IMO,TaskScheduler可以通过将delgate建模为特定专业化中的任务来代替SynchrinizationContext