我刚刚在阅读 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服务器可以假定客户端的身份,以便服务器只能访问客户端被允许访问的资源。当线程池线程生成异步操作时,它可能会由另一个线程池线程完成,后者将处理异步操作的结果。在代表原始客户端请求执行此工作时,文化和身份需要“流动”到新的线程池线程,以便代表客户端执行的任何附加工作都使用客户端的文化和身份信息。
简单地说,一个基于SynchronizationContext
的对象连接了应用程序模型与其线程模型。FCL定义了几个派生自SynchronizationContext的类,但通常您不会直接处理这些类; 实际上,其中许多类没有公开暴露或记录。
对于大多数应用程序开发者而言,他们无需了解SynchronizationContext
类的任何内容。当您await
一个Task
时,会获取调用线程的SynchronizationContext
对象。当线程池线程完成任务时,将使用SynchronizationContext
对象,确保应用程序模型的正确线程模型。因此,当GUI线程await
一个Task
时,紧随await
运算符的代码也一定会在GUI线程上执行,从而允许该代码更新UI元素。对于ASP.NET应用程序,跟随await
运算符的代码保证在具有客户端文化和主体信息关联的线程池线程上执行。
如果您有特殊的任务调度需求,当然可以定义一个从TaskScheduler
派生的自定义类。Microsoft提供了许多示例代码和任务调度器源代码,包括在Parallel Extensions Extras包中。例如:IOTaskScheduler
, LimitedConcurrencyLevelTaskScheduler
, OrderedTaskScheduler
, PrioritizingTaskScheduler
, ThreadPerTaskScheduler
。