我刚刚在读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
该类的信息。当你await
aTask
时,调用线程的SynchronizationContext
对象被获取。当线程池线程完成时Task
,将使用该SynchronizationContext
对象,确保您的应用程序模型具有正确的线程模型。因此,当GUI 线程
awaits
a时Task
,操作符后面的代码await
也保证在 GUI 线程上执行,从而允许该代码更新 UI 元素。对于 ASP.NET 应用程序,await 运算符后面的代码保证在线程池线程上执行,该线程具有与其相关联的客户端文化和主体信息。
TaskScheduler
当然,如果您有特殊的任务调度需求,您可以定义自己的派生类。微软为任务提供了一堆示例代码,并在 Parallel Extensions Extras 包中包含了一堆任务调度程序的源代码。比如,IOTaskScheduler
, LimitedConcurrencyLevelTaskScheduler
, OrderedTaskScheduler
, PrioritizingTaskScheduler
, ThreadPerTaskScheduler
.