很遗憾,很多开发人员甚至不知道这个有用的工具。

无论是什么平台(ASP.NET、Windows 窗体、Windows Presentation Foundation (WPF)、Silverlight 或其他),所有 .NET 程序都包含 SynchronizationContext 概念,并且所有多线程编程人员都可以通过理解和应用它获益。

SynchronizationContext 的必要性

每个要以这种方式使用 Windows 消息队列的多线程程序都必须定义自己的自定义 Windows 消息以及处理约定。

ISynchronizeInvoke 诞生了。

Windows 窗体提供了唯一的 ISynchronizeInvoke 实现,并且开发了一种模式来设计异步组件,这样皆大欢喜。

使用异步页面,处理请求的线程可以开始每个操作,然后返回到 ASP.NET 线程池;当操作结束时,ASP.NET 线程池的另一个线程可以完成该请求。

经过精心设计,SynchronizationContext 取代了 ISynchronizeInvoke。

SynchronizationContext 的概念

设计 SynchronizationContext 是为了替代 ISynchronizeInvoke,但完成设计后,它就不仅仅是一个替代品了。

SynchronizationContext 不包含用来确定是否必须同步的机制,因为这是不可能的。

线程可以更改其当前上下文,但这样的情况非常少见。

大多数情况下,捕获到当前 SynchronizationContext 时,计数递增;捕获到的 SynchronizationContext 用于将完成通知列队到上下文中时,计数递减。

图 1 中列出了一些最为重要的方面。

图 1 SynchronizationContext API 的各方面

 
// The important aspects of the SynchronizationContext APIclass SynchronizationContext

{

  // Dispatch work to the context.
void Post(..); // (asynchronously)

  void Send(..); // (synchronously)

  // Keep track of the number of asynchronous operations.
void OperationStarted();

  void OperationCompleted();

  // Each thread has a current context.
// If "Current" is null, then the thread's current context is


  // "new SynchronizationContext()", by convention.
static SynchronizationContext Current { get; }

  static void SetSynchronizationContext(SynchronizationContext);
}

SynchronizationContext 的实现

我将简单讨论部分实现。

WindowsFormsSynchronizationContext 的上下文是一个单独的 UI 线程。

当前实现为每个 UI 线程创建一个 WindowsFormsSynchronizationContext。

DispatcherSynchronizationContext 的上下文是一个单独的 UI 线程。

当前实现为每个顶层窗口创建一个 DispatcherSynchronizationContext,即使它们都使用相同的基础调度程序也是如此。

根据惯例,如果一个线程的当前 SynchronizationContext 为 null,那么它隐式具有一个默认 SynchronizationContext。

从这种意义上讲,默认上下文可以包含进程中的所有 线程。

因此,UI 应用程序通常有两个同步上下文:包含 UI 线程的 UI SynchronizationContext 和包含 ThreadPool 线程的默认 SynchronizationContext。

在只有一个 BackgroundWorker 的情况下,这通常是基于 UI 的 SynchronizationContext,因此 RunWorkerCompleted 在 RunWorkerAsync 捕获的 UI 上下文中执行(请参见图 2)。

SynchronizationContext 综述

图 2 UI 上下文中只有一个 BackgroundWorker

在这种情况下,嵌套的 RunWorkerAsync 将捕获默认 SynchronizationContext,因此它将对一个 ThreadPool 线程而不是 UI 线程执行其 RunWorkerCompleted(请参见图 3)。

SynchronizationContext 综述

图 3 UI 上下文中的嵌套 BackgroundWorker

nitoasync.codeplex.com) 可用作通用 SynchronizationContext 实现。

即使委托是通过调用 Post“异步”列队的,也会直接调用委托。

这些可能是启动请求的线程,但更可能是操作完成时处于空闲状态的任何线程。

它们可以在任意线程上执行,但该线程将具有原始页面的标识和区域。

该上下文将保持在相同的线程中,但会确保事件处理程序使用正确的标识和区域运行。

有关 SynchronizationContext 实现的注意事项

但是,在设计这类可重用组件时,必须注意几点。

不过,这不是多大的缺点;代码更为清晰,并且更容易验证它是否始终在已知上下文中执行,而不是试图处理多个上下文。

默认 SynchronizationContext 不保证执行顺序或同步顺序。

一般而言,最好不要假设任何上下文实例将在任何指定线程上运行。

图 4 总结了这些不同的实现。

图 4 SynchronizationContext 实现摘要

  使用特定线程执行委托 独占(一次执行一个委托) 有序(委托按队列顺序执行) Send 可以直接调用委托 Post 可以直接调用委托
Windows 窗体 如果从 UI 线程调用 從不
WPF/Silverlight 如果从 UI 线程调用 從不
默认 不能 不能 不能 Always 從不
ASP.NET 不能 不能 Always Always

AsyncOperationManager 和 AsyncOperation

AsyncOperation 将委托异步发布到捕获的 SynchronizationContext。

对于这些类型的操作,可以直接捕获和使用 SynchronizationContext。

基于任务的 API 是 .NET 中异步编程的发展方向。

SynchronizationContext 的库支持示例

通过使用 SynchronizationContext 公开 API,库不仅获得了框架独立性,而且为高级最终用户提供了一个可扩展点。

当恢复 ExecutionContext 时,通常也会恢复 SynchronizationContext。

Windows Communication Foundation (WCF):此特性的默认值为 true,这表示在创建通信通道时捕获当前 SynchronizationContext,这一捕获的 SynchronizationContext 用于使约定方法列队。

在这类情况下,将 UseSynchronizationContext 设置为 false 可以禁止 WCF 自动使用 SynchronizationContext。

msdn.microsoft.com/magazine/cc163321)。

部分 .NET Framework 4 升级在 WorkflowInstance 类及其派生类 derived WorkflowApplication 上包含 SynchronizationContext 属性。

然后该 SynchronizationContext 用于发布工作流完成事件以及工作流活动。

UI 更新的进度报告可以在一个嵌套任务中完成,如图 5 所示。

图 5 UI 更新的进度报告

 
private void button1_Click(object sender, EventArgs e)
{
  // This TaskScheduler captures SynchronizationContext.Current.
TaskScheduler taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  // Start a new task (this uses the default TaskScheduler, 
  // so it will run on a ThreadPool thread).
Task.Factory.StartNew(() =>
  {
    // We are running on a ThreadPool thread here.
; // Do some work.
// Report progress to the UI.
Task reportProgressTask = Task.Factory.StartNew(() =>
      {
        // We are running on the UI thread here.
; // Update the UI with our progress.
},
      CancellationToken.None,
      TaskCreationOptions.None,
      taskScheduler);
    reportProgressTask.Wait();
  
    ; // Do more work.
});
}

如果出现取消请求,CancellationToken 将该委托列入 SynchronizationContext 队列而不是直接执行它。

Microsoft 被动扩展 (Rx):ObserveOn、 ObserveOn 通常用于使用传入事件更新 UI,SubscribeOn 用于从 UI 对象使用事件。

Rx 包含 SynchronizationContextScheduler,这是一个列入 SynchronizationContext 的 IScheduler 实现。

Visual Studio Async CTP:await、ConfigureAwait、 默认情况下,当前 SynchronizationContext 在一个等待点捕获,此 SynchronizationContext 用于在等待后继续(更确切地说,仅当它不为 null 时,才捕获当前 SynchronizationContext,如果为 null,则捕获当前 TaskScheduler):

 
private async void button1_Click(object sender, EventArgs e)
{
  // SynchronizationContext.Current is implicitly captured by await.
var data = await webClient.DownloadStringTaskAsync(uri);

  // At this point, the captured SynchronizationContext was used to resume
  // execution, so we can freely update UI objects.
}

SynchronizationContext 实例还有一种扩展方法 SwitchTo;使用该方法,任何异步方法都可以通过调用 SwitchTo 并等待结果,更改为不同的 SynchronizationContext。

该类在构造时捕获当前 SynchronizationContext 并在此上下文中引发其 ProgressChanged 事件。

这一行为使返回 void 的异步方法类似于顶层异步操作。

限制和功能

技术精湛的编程人员了解 SynchronizationContext 限制和功能后,可以更好地编写和利用这些类。

 

nitoprograms.com。

相关文章: