【问题标题】: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.EventLoopSchedulerDispatcherThreadPoolTaskSchedulerSyncrhonizationContextIScheduler 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 返回任务的扩展方法。

标签: c# multithreading task-parallel-library system.reactive conceptual


【解决方案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 视为实现(任务)调度程序的“低级”“特定于案例”的构建块。但不是正交的!
    猜你喜欢
    • 2019-05-17
    • 2012-06-02
    • 2023-03-10
    • 2016-04-24
    • 1970-01-01
    • 2017-02-06
    • 2018-08-08
    • 2011-03-07
    • 2013-12-23
    相关资源
    最近更新 更多