Microsoft .NET Framework 新的 SynchronizationContext 类






4.88/5 (83投票s)
2006 年 5 月 27 日
9分钟阅读

412371
一个全新的 .NET Framework 类,用于更轻松地管理线程同步问题。
引言
SynchronizationContext
类是 .NET Framework System.Threading
命名空间下的一个新类。该类的目的是提供一种模型,使线程间的通信更加容易和健壮。我将首先介绍 SynchronizationContext
类如何帮助我们使用 Windows Forms 处理事件。我还会提及 System.ComponentModel
下其他一些使用 SynchronizationContext
类来帮助我们解决同步问题的类。
需要声明的是,以下部分内容是我根据对这些新类的简短经验总结出来的,而非直接来源于文档。目前文档似乎有些 sparse。因此,尽管我的理解还不完整,但我希望以下内容能有所帮助,并且我能在未来随着我的理解(以及微软的文档)的深入而扩展这篇文章。
背景
我们大多数人都知道,禁止在创建控件的线程以外的任何线程上修改 Control
。相反,在另一个线程上生成的事件必须通过其(或所属的 Form
)的 Invoke
或 BeginInvoke
方法,将其封送到 Control
的线程。这两个方法属于 ISynchronizeInvoke
接口,Control
类实现了该接口。它们的作用是接受一个委托,并在 ISynchronizeInvoke
对象正在运行的同一线程上调用该委托。通常,Form
用于响应另一个线程上生成的事件的方法如下所示:
private void HandleSomeEvent(object sender, EventArgs e)
{
if(InvokeRequired)
{
BeginInvoke(new EventHandler(HandleSomeEvent), sender, e);
}
else
{
// Event logic here.
}
}
此方法首先检查其 InvokeRequired
布尔属性,该属性也属于 ISynchronizeInvoke
接口,以查看是否需要将事件封送到其线程。如果该属性为 true
,它会调用 BeginInvoke
方法,并将处理事件的方法及其参数的委托传递给它。如果该属性为 false
,它将执行其响应事件的逻辑。换句话说,如果 InvokeRequired
属性在非 Form
所属的线程上被检查,则它将为 true
;否则,它将为 false
。
请注意,传递给 BeginInvoke
的委托代表了最初处理事件的同一个方法。如果 InvokeRequired
属性为 true
,这将导致事件处理程序方法被调用两次:一次是在响应事件时首次调用,另一次是在通过调用 BeginInvoke
被 Form
封送之后。第二次,InvokeRequired
属性将为 false
,并且事件逻辑将运行。
SynchronizationContext 类来帮忙!
如果 Form
不需要检查事件是否在另一个线程上引发,那不是很好吗?如果 Form
在不检查其 InvokeRequired
属性的情况下就可以响应事件,那不是很好吗?SynchronizationContext
类解决了我们的问题。
SynchronizationContext
类代表了一个通道,我们可以通过它传递委托,以便在 SynchronizationContext
所代表的同一线程上调用这些委托。在这方面,它类似于 ISynchronizeInvoke
接口。对应于 ISynchronizeInvoke
的 Invoke
和 BeginInvoke
方法,SynchronizationContext
类具有 Send
和 Post
方法。与 Invoke
方法类似,SynchronizationContext
的 Send
方法用于同步调用委托,而与 BeginInvoke
方法类似,Post
方法用于异步调用委托。
Send
和 Post
方法都接受一个 SendOrPostCallback
委托,该委托代表要调用的方法。此外,它们还接受一个对象,该对象表示状态信息,用于在调用委托时传递给委托。
此时,我们可能会想,SynchronizationContext
类有什么优势?为什么不使用 ISynchrnoizeInvoke
接口呢?
好了,这里就变得有趣了。SynchronizationContext
类有一个静态方法 SetSynchronizationContext
,用于设置当前 SynchronizationContext
对象。幕后发生的是,SetSynchronizationContext
静态方法接受传递给它的 SynchronizationContext
对象,并将其与当前线程关联起来,即调用 SetSynchronizationContext
方法的线程。之后,可以使用 Current
静态属性检索 SynchronizationContext
对象。该属性代表当前线程的 SynchronizationContext
对象,无论您在访问 Current
属性时身处哪个线程。
让我们再回顾一遍
当调用 SetSynchronizationContext
静态方法时,它接受传递给它的 SynchronizationContext
对象,并跟踪它及其所属的线程。它确保当您访问 Current
静态属性时,您将获得为该线程设置的 SynchronizationContext
对象。
因此,回到上面描述的 Form
场景,当创建一个 Form
时,会设置一个代表 Form
线程的 SynchronizationContext
对象(实际上是一个从 SynchronizationContext
派生的类对象,称为 WindowsFormsSynchronizationContext
,稍后将详细介绍)。其他对象可以通过 Current
属性检索 Form
的 SynchronizationContext
对象,并稍后使用它将委托调用封送到 Form
的线程。这样,Form
不再需要检查它们正在响应的事件是否源自另一个线程。封送操作已经在其他地方处理完毕。本质上,封送事件的责任已从事件的接收者转移到事件的发送者。
这种模型有助于我们自动化线程间的通信。线程可以通过使用另一个线程的 SynchronizationContext
对象来安全地与其通信。
现在我需要告诉您一件事:SynchronizationContext
类本身并没有太多独立的功能。虽然它不是一个抽象类,但它实际上是用作派生类的基类,这些派生类会重写其方法以提供有意义的功能。例如,这是它对 Send
和 Post
方法的实现:
public virtual void Send(SendOrPostCallback d, Object state)
{
d(state);
}
public virtual void Post(SendOrPostCallback d, Object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}
Send
方法简单地调用回调委托,并将状态参数传递给它。Post
方法通过将回调排队作为 ThreadPool
的工作项来提供异步行为。因此,SynchronizationContext
类的默认行为并没有真正做太多事情。当我们使用或创建派生类来提供更有意义的功能时,就会体现出它的魔力。
WindowsFormsSynchronizationContext
类是 SynchronizationContext
派生类的一个例子。Form
使用此类来表示其同步上下文。我猜想此类是 Form
的 ISynchronizeInvoke
功能的一个轻量级包装器,分别委托调用 Send
和 Post
到其 Invoke
和 BeginInvoke
方法。
使用 SynchronizationContext 类
我们如何编写使用 SynchronizationContext
类的自己的类?需要记住的关键是,我们的目标是获取我们类创建所在线程的 SynchronizationContext
,以便以后可以从另一个线程使用它来将事件发送/发布到原始线程。我将给出一个非常简单的例子。这个例子没有做任何有用的事情,但几乎一眼就能看出它展示了使用 SynchronizationContext
类的基本模板:
using System;
using System.Threading;
namespace SynchronizationContextExample
{
public class MySynchronizedClass
{
private Thread workerThread;
private SynchronizationContext context;
public event EventHandler SomethingHappened;
public MySynchronizedClass()
{
// It's important to get the current SynchronizationContext
// object here in the constructor. We want the
// SynchronizationContext object belonging to the thread in
// which this object is being created.
context = SynchronizationContext.Current;
// It's possible that the current thread does not have a
// SynchronizationContext object; a SynchronizationContext
// object has not been set for this thread.
//
// If so, we simplify things by creating a SynchronizationContext
// object ourselves. However! There could be some problems with
// this approach. See the article for more details.
if(context == null)
{
context = new SynchronizationContext();
}
workerThread = new Thread(new ThreadStart(DoWork));
workerThread.Start();
}
private void DoWork()
{
context.Post(new SendOrPostCallback(delegate(object state)
{
EventHandler handler = SomethingHappened;
if(handler != null)
{
handler(this, EventArgs.Empty);
}
}), null);
}
}
}
此类在其构造函数中获取当前线程的 SynchronizationContext
。如果此类在 Form
中使用,那么当前的 SynchronizationContext
对象将允许我们将事件发送/发布到 Form
的线程。然而,如果此类的一个实例被创建在某个工作线程中,SynchronizationContext.Current
属性可能为 null
。换句话说,当前线程的 SynchronizationContext
对象可能尚未设置。因此,检查 Current
属性是否为 null
很重要。在这里,在它为 null
的情况下,我只是创建了一个 SynchronizationContext
类的实例,并依赖于其默认行为。
好处是,我们的类不必知道它正在向谁发送/发布事件。它不必传递一个 ISynchronizeInvoke
对象来同步自身;它可以通过 SynchronizationContext.Current
属性访问当前的 SynchronizationContext
对象。
警告! 正如本文 消息板上的帖子 指出的那样,创建 SynchronizationContext
类的实例可能很危险。例如,假设您正在编写一个基于 Form
的应用程序,并且某个对象需要以线程安全的方式与主 Form
进行交互;该对象需要访问属于 Form
线程的 SynchronizationContext
。该对象可以通过访问 SynchronizationContext.Current
属性来检索此信息。这将为您提供 Form
的 SynchronizationContext
派生对象,前提是 Current
属性在 Form
运行的同一线程上被检查。如果它从另一个线程进行检查,Current
属性可能会为 null。在这种情况下,将其视为错误比简单地创建 SynchronizationContext
类的实例更安全。
AsyncOperation 和 AsyncOperationManager 类
AsyncOperation
和 AsyncOperationManager
类通过消除直接访问 SynchronizationContext
对象的需要,使我们的生活更加轻松。这是使用这两个类重写的上述示例:
using System;
using System.Threading;
using System.ComponentModel;
namespace SynchronizationContextExample
{
public class MySynchronizedClass
{
private Thread workerThread;
private AsyncOperation operation;
public event EventHandler SomethingHappened;
public MySynchronizedClass()
{
operation = AsyncOperationManager.CreateOperation(null);
workerThread = new Thread(new ThreadStart(DoWork));
workerThread.Start();
}
private void DoWork()
{
operation.Post(new SendOrPostCallback(delegate(object state)
{
EventHandler handler = SomethingHappened;
if(handler != null)
{
handler(this, EventArgs.Empty);
}
}), null);
operation.OperationCompleted();
}
}
}
看起来 AsyncOperationManager
负责检查当前的 SynchronizationContext
对象是否为 null
,并在该情况下为我们提供一个(封装在 AsyncOperation
对象中)。
BackgroundWorker 类
使用此新模型的类的一个例子是 BackgroundWorker
类。此类允许您在执行其他工作的同时在后台运行操作。它提供用于引发事件的功能,以便您了解操作的进度以及何时完成。当将 BackgroundWorker
与 Form
一起使用时,Form
不需要检查 BackgroundWorker
的事件是否是从不同的线程引发的。BackgroundWorker
类通过使用 Form
的 SynchronizationContext
对象为我们处理了这个问题。
结论
SynchronizationContext
类中还有一些我没有涵盖的方法。老实说,我不确定它们的用途。希望将来我能为这篇文章添加更多细节,以提供对这个新颖且有用的类的更深入的理解。事实上,我鼓励您通过评论和建议来让这篇文章更有信息量。我对这些新类感到非常兴奋,因为我相信微软在编程方式上正在不断进步。
希望您喜欢这篇文章,并期待您的反馈。谢谢。
历史
- 2006 年 5 月 26 日 - 初稿完成。
- 2006 年 5 月 7 日 - 更新了关于使用原始
SynchronizationContext
对象的警告。