41

Stephen Toub 在博客中写道

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

因此System.Reactive.Concurrency.EventLoopScheduler,响应式扩展的DispatcherThreadPoolTaskSchedulerSyncrhonizationContextIScheduler 实现在这个意义上都是“调度程序”。

它们之间有什么区别?

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

Stephen Cleary 的优秀文章解释了 SyncrhonizationContext,我想我明白了。那么为什么我们需要TaskScheduler,目前还不清楚。

请解释或指出来源。

4

3 回答 3

22

我刚刚在读CLR via C#Jeffrey Ritcher 的书,多亏了他,我也可以给出一些与该主题相关的简单解释。(假设我不完全同意答案中的全部细节)

首先,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该类的信息。当你awaitaTask时,调用线程的SynchronizationContext 对象被获取。当线程池线程完成时Task,将使用该SynchronizationContext 对象,确保您的应用程序模型具有正确的线程模型。因此,当GUI 线程 awaitsa时Task,操作符后面的代码await也保证在 GUI 线程上执行,从而允许该代码更新 UI 元素。对于 ASP.NET 应用程序,await 运算符后面的代码保证在线程池线程上执行,该线程具有与其相关联的客户端文化和主体信息

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

于 2019-03-10T07:23:33.197 回答
16

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

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

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

于 2013-04-15T17:14:22.070 回答
11

虽然,如引述,

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

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

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

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

于 2012-03-06T09:31:27.780 回答