SynchronizationContext和TaskScheduler之间的概念区别是什么

问题描述 投票:21回答:3

Stephen Toub blogged

SynchronizationContext和TaskScheduler都是表示“调度程序”的抽象,是您提供一些工作的东西,它决定了运行该工作的时间和地点。有许多不同形式的调度程序。例如,ThreadPool是一个调度程序:您调用ThreadPool.QueueUserWorkItem来提供一个委托来运行,该委托被排队,并且其中一个ThreadPool的线程最终选择并运行该委托。您的用户界面还有一个调度程序:消息泵。

所以System.Reactive.Concurrency.EventLoopSchedulerDispatcherThreadPoolTaskSchedulerSyncrhonizationContextIScheduler implementations of Reactive Extensions都是这种意义上的“调度员”。

他们之间有什么区别?

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

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

请解释或指向消息来源。

c# multithreading task-parallel-library system.reactive conceptual
3个回答
11
投票

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

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

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


8
投票

虽然,如引用,

SynchronizationContext和TaskScheduler都是代表“调度程序”的抽象

IMO,抽象程度(以及API)不同。在某种意义上,SynchronizationContext是一种更通用的API,Post / Send采用简单的方法委托。

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

SynchronizationContext是在.NET 2.0中引入的,而TPL是在.NET 4中引入的。有趣的是,如果序列是相反的,那么FCL设计者会选择什么,即如果在.NET 2.0时存在TPL会怎么样。通过将delgates建模为特定专业化中的任务,可以使用IMO,TaskScheduler代替SynchrinizationContext。


7
投票

我刚刚阅读了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类的任何信息。等待任务时,获取调用线程的SynchronizationContext对象。当线程池线程完成Task时,将使用SynchronizationContext对象,从而确保应用程序模型的正确线程模型。因此,当GUI线程awaits任务时,await运算符后面的代码也保证在GUI线程上执行,允许该代码更新UI元素。对于ASP.NET应用程序,保证在await运算符后面的代码在具有与之关联的客户端文化和主体信息的线程池线程上执行。

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

© www.soinside.com 2019 - 2024. All rights reserved.