SynchronizationContext -MSDN 很让人失望
我不知道为什么,目前在.Net下关于这个类只有很少的资料。MSDN文档也只有很少的关于如何使用SynchronizationContext的资料。最开始的时候,我不得不说我在理解为什么有这个类以及怎么使用这个类上经历了一段困难的时间。通过阅读大量的相关资料,我最终搞明白了这个类的目的以及它应该如何去使用。我决定写这篇文章来帮助其他开发者理解如何使用这个类,这个类能干嘛以及它不能干嘛。
使用SynchronizationContext来封装一段来自一个线程的代码到另一个线程执行
让我们先来了解一些不常见的技术点以帮助我们展示如何使用这个类。SynchronizationContext可以使一个线程与另一个线程进行通信。假设你又两个线程,Thead1和Thread2。Thread1做某些事情,然后它想在Thread2里面执行一些代码。一个可以实现的方式是请求Thread得到SynchronizationContext这个对象,把它给Thread1,然后Thread1可以调用SynchronizationContext的send方法在Thread2里面执行代码。听起来很绕口...但是这就是你需要了解的东西。不是每一个线程都有一个SynchronizationContext对象。一个总是有SynchronizationContext对象的是UI线程。
谁把SynchronizationContext对象放到UI线程里的?有没有可以猜一下的?放弃思考了?好吧,我来告诉你答案吧。答案是在这个线程里的一个控件(control)被创建的时候会把SynchronizationContext对象放到这个线程里。一般来说,第一次会是form第一次创建的时候。我怎么知道的?好吧,我一会就给你证明。
因为我的代码用了SynchronizationContext.Current,所以就让我先来解释下这个静态属性到底能干嘛吧。SynchronizationContext.Current可以使我们得到一个当前线程的SynchronizationContext的对象。我们必须清楚如下问题:SynchronizationContext.Current对象不是一个AppDomain一个实例的,而是每个线程一个实例。这就意味着两个线程在调用Synchronization.Current时将会拥有他们自己的SynchronizationContext对象实例。如果你好奇这个context上下文对象怎么存储的,那么答案就是它存储在线程data store(就像我之前说的,不是在appDomain的全局内存空间)。
来吧,让我们看下在我们UI线程中使用的SynchronizationContext的代码吧。
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// let's check the context here
var context = SynchronizationContext.Current;
if (context == null)
MessageBox.Show("No context for this thread");
else
MessageBox.Show("We got a context");
// create a form
Form1 form = new Form1();
// let's check it again after creating a form
context = SynchronizationContext.Current;
if (context == null)
MessageBox.Show("No context for this thread");
else
MessageBox.Show("We got a context");
if (context == null)
MessageBox.Show("No context for this thread");
Application.Run(new Form1());
}
正如你所见,有如下几个点需要注意:
- 第一个messagebox将会显示这个线程没有context。因为.Net都不知道这个线程就会做什么,因此没有一个运行时类来在为这个线程初始化sync Context对象。
- 在form创建之后,我们就会发现context已经被设置了。Form类负责了这件事情。它会检测sync Context是否已经有了,如果没有,它就会给线程设置一个。记住context对象在一个线程里面是一样的,所以任何UI控件都可以访问它,因为所有的UI操作都必须在UI线程里面执行。再通俗点说,创建window的线程都可以与window通信。在我们的场景下,这个线程就是应用程序的主线程。
怎么使用它?
既然UI线程已经足够nice了,它给了我们一个Sync Context来使我们在UI线程下执行代码,那么我们如何写呢?
第一步,我们要确定我们真有需要给UI线程封送的代码么?答案肯定是“是”。如果你在一个不是UI线程的线程里面执行代码,但你不得不去更新UI。要不要成为一个尝试下的英雄?可惜的是,你会得到一个异常(在.net1.0都没有引发异常,它只会让你的程序挂掉,在.net2.0中,就会给你引发一个很恶心的异常)。
公平的说,我得说你不是必须得用这个类来与UI线程进行通信。你可以使用InvokeRequired属性(在每个UI control里面都有)来封送你的代码。如果你通过InvokeRequired得到一个“true”,你就可以使用Control.Invoke方法来封送代码到UI线程。非常好!那为什么还要继续读我的文章呢?但是,这个技术一个问题,你必须得有一个Control你才能调用invoke方法。在UI线程里面这没有什么,但是如果在非UI的线程里面,你如果还想封送代码,你就只能在你的非UI线程里面增加一个control了。从设计的角度来说,在业务逻辑层应该永远都没有一个UI的引用。所以,你将所有的同步代码都放到了UI类里面来让IU保证封送。但是,这将会增加UI的功能复杂度,使UI完成比我们希望的多得多的功能。我必须说,让一个没有任何Control或者Form引用的业务逻辑层来负责封送代码到UI层是一个更好的选择。
那到底怎么做呢?
简单来说,创建一个线程,给他sync context对象,然后就可以用这个对象给UI线程封送代码了。让我们看个例子吧。
在接下来的例子中,有一个list box在工作线程调用。这个线程完成了一些计算然后写到UI的listbox里面。这个线程通过mToolStripButtonThreads_Click事件响应来更新UI。
首先,让我们先看下form:
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources =
new System.ComponentModel.ComponentResourceManager(typeof(Form1));
this.mListBox = new System.Windows.Forms.ListBox();
this.toolStrip1 = new System.Windows.Forms.ToolStrip();
this.mToolStripButtonThreads = new System.Windows.Forms.ToolStripButton();
this.toolStrip1.SuspendLayout();
this.SuspendLayout();
//
// mListBox
//
this.mListBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.mListBox.FormattingEnabled = true;
this.mListBox.Location = new System.Drawing.Point(0, 0);
this.mListBox.Name = "mListBox";
this.mListBox.Size = new System.Drawing.Size(284, 264);
this.mListBox.TabIndex = 0;
//
// toolStrip1
//
this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
this.mToolStripButtonThreads});
this.toolStrip1.Location = new System.Drawing.Point(0, 0);
this.toolStrip1.Name = "toolStrip1";
this.toolStrip1.Size = new System.Drawing.Size(284, 25);
this.toolStrip1.TabIndex = 1;
this.toolStrip1.Text = "toolStrip1";
//
// mToolStripButtonThreads
//
this.mToolStripButtonThreads.DisplayStyle =
System.Windows.Forms.ToolStripItemDisplayStyle.Text;
this.mToolStripButtonThreads.Image = ((System.Drawing.Image)
(resources.GetObject("mToolStripButtonThreads.Image")));
this.mToolStripButtonThreads.ImageTransparentColor =
System.Drawing.Color.Magenta;
this.mToolStripButtonThreads.Name = "mToolStripButtonThreads";
this.mToolStripButtonThreads.Size = new System.Drawing.Size(148, 22);
this.mToolStripButtonThreads.Text = "Press Here to start threads";
this.mToolStripButtonThreads.Click +=
new System.EventHandler(this.mToolStripButtonThreads_Click);
//
// Form1
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(284, 264);
this.Controls.Add(this.toolStrip1);
this.Controls.Add(this.mListBox);
this.Name = "Form1";
this.Text = "Form1";
this.toolStrip1.ResumeLayout(false);
this.toolStrip1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.ListBox mListBox;
private System.Windows.Forms.ToolStrip toolStrip1;
private System.Windows.Forms.ToolStripButton mToolStripButtonThreads;
}
现在让我们看这个例子:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void mToolStripButtonThreads_Click(object sender, EventArgs e)
{
// let's see the thread id
int id = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id);
// grab the sync context associated to this
// thread (the UI thread), and save it in uiContext
// note that this context is set by the UI thread
// during Form creation (outside of your control)
// also note, that not every thread has a sync context attached to it.
SynchronizationContext uiContext = SynchronizationContext.Current;
// create a thread and associate it to the run method
Thread thread = new Thread(Run);
// start the thread, and pass it the UI context,
// so this thread will be able to update the UI
// from within the thread
thread.Start(uiContext);
}
private void Run(object state)
{
// lets see the thread id
int id = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("Run thread: " + id);
// grab the context from the state
SynchronizationContext uiContext = state as SynchronizationContext;
for (int i = 0; i < 1000; i++)
{
// normally you would do some code here
// to grab items from the database. or some long
// computation
Thread.Sleep(10);
// use the ui context to execute the UpdateUI method,
// this insure that the UpdateUI method will run on the UI thread.
uiContext.Post(UpdateUI, "line " + i.ToString());
}
}
/// <summary>
/// This method is executed on the main UI thread.
/// </summary>
private void UpdateUI(object state)
{
int id = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("UpdateUI thread:" + id);
string text = state as string;
mListBox.Items.Add(text);
}
}
先浏览一遍这个代码。你应该注意到我将每个方法的线程ID都打印出来来方便我们一会回顾。
比如:
// let's see the thread id
int id = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id);
当点击toolstrip button的时候,一个线程将会执行Run方法,需要注意的是我给这个线程传了一个state进去。我在调用的时候传入了UI线程的sync context对象。
SynchronizationContext uiContext = SynchronizationContext.Current;
因为我在toolstrip button的事件响应中执行,所以我知道我在UI线程中。通过调用SynchronizationContext.Current,我可以从UI线程得到sync context对象。
Run 将从它的state里面得到SynchronizationContext对象,这样它就有了像UI线程封送的代码的能力。
// grab the context from the state
SynchronizationContext uiContext = state as SynchronizationContext;
Run方法将会在listbox里面写1000行。怎么办呢?需要先用SynchronizationContext中的send方法:
public virtual void Send(SendOrPostCallback d, object state);
SynchronizationContext.Send方法有两个参数,一个是指向一个方法的委托,一个是"state"对象。在我们的例子里是这样的:
uiContext.Send(UpdateUI, "line " + i.ToString());
UpdateUI就是我们给这个委托传的方法,“state”是我们想给listbox增加的string。在UpdateUI中的代码是在UI线程中执行而不是在调用的线程。
private void UpdateUI(object state)
{
int id = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("UpdateUI thread:" + id);
string text = state as string;
mListBox.Items.Add(text);
}
注意到这个代码直接在UI线程中执行。这里没有检查InvokerRequired,因为我知道由于使用了UI线程中的SynchronizationContext的send方法,它就会运行在UI线程中。
让我们来看下线程的id:
mToolStripButtonThreads_Click thread: 10
Run thread: 3
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
UpdateUI thread:10
... (x1000 times)
上面可以看到UI线程是10,工作线程(Run)是3,当我们更新UI的时候,我们又回到了线程10(UI线程)。这样所有的事情就像我们想的一样。
错误处理
很好,我们已经有能力像UI线程封送代码了,但是当我们封送的代码有引发异常的时候会怎么样?谁来捕获它呢?UI线程还是工作线程?
private void Run(object state)
{
// let's see the thread id
int id = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("Run thread: " + id);
// grab the context from the state
SynchronizationContext uiContext = state as SynchronizationContext;
for (int i = 0; i < 1000; i++)
{
Trace.WriteLine("Loop " + i.ToString());
// normally you would do some code here
// to grab items from the database. or some long
// computation
Thread.Sleep(10);
// use the ui context to execute the UpdateUI method, this insure that the
// UpdateUI method will run on the UI thread.
try
{
uiContext.Send(UpdateUI, "line " + i.ToString());
}
catch (Exception e)
{
Trace.WriteLine(e.Message);
}
}
}
/// <summary>
/// This method is executed on the main UI thread.
/// </summary>
private void UpdateUI(object state)
{
throw new Exception("Boom");
}
我修改了UpdateUI方法来抛出一个异常:
throw new Exception("Boom");
当然,我也同时修改了Run方法在Send方法增加了try/catch。
try
{
uiContext.Send(UpdateUI, "line " + i.ToString());
}
catch (Exception e)
{
Trace.WriteLine(e.Message);
}
当执行这个代码的时候,我发现异常在Run方法中被捕获而不是在UI线程中。这很有趣,因为我们本以为会是UI线程由于没有异常处理而挂掉。
综上,Send方法施展了一个小魔法:它让我们的代码在别的线程执行,但是在当前线程引发异常。
Send 还是 Post
Send只是我们可以向UI线程封送代码的一种方式。另一种是Post。两者之间有什么不同呢?很多!可能现在需要更多的了解这个类的细节了,我们先来看下SynchronizationContext的接口:
// Summary:
// Provides the basic functionality for propagating a synchronization context
// in various synchronization models.
public class SynchronizationContext
{
// Summary:
// Creates a new instance of the System.Threading.SynchronizationContext class.
public SynchronizationContext();
// Summary:
// Gets the synchronization context for the current thread.
//
// Returns:
// A System.Threading.SynchronizationContext object representing the current
// synchronization context.
public static SynchronizationContext Current { get; }
// Summary:
// When overridden in a derived class, creates a copy of the synchronization
// context.
//
// Returns:
// A new System.Threading.SynchronizationContext object.
public virtual SynchronizationContext CreateCopy();
//
// Summary:
// Determines if wait notification is required.
//
// Returns:
// true if wait notification is required; otherwise, false.
public bool IsWaitNotificationRequired();
//
// Summary:
// When overridden in a derived class, responds to the notification that an
// operation has completed.
public virtual void OperationCompleted();
//
// Summary:
// When overridden in a derived class, responds to the notification that an
// operation has started.
public virtual void OperationStarted();
//
// Summary:
// When overridden in a derived class, dispatches an asynchronous message to
// a synchronization context.
//
// Parameters:
// d:
// The System.Threading.SendOrPostCallback delegate to call.
//
// state:
// The object passed to the delegate.
public virtual void Post(SendOrPostCallback d, object state);
//
// Summary:
// When overridden in a derived class, dispatches a synchronous message to a
// synchronization context.
//
// Parameters:
// d:
// The System.Threading.SendOrPostCallback delegate to call.
//
// state:
// The object passed to the delegate.
public virtual void Send(SendOrPostCallback d, object state);
//
// Summary:
// Sets the current synchronization context.
//
// Parameters:
// syncContext:
// The System.Threading.SynchronizationContext object to be set.
public static void SetSynchronizationContext(SynchronizationContext syncContext);
//
// Summary:
// Sets notification that wait notification is required and prepares the callback
// method so it can be called more reliably when a wait occurs.
protected void SetWaitNotificationRequired();
//
// Summary:
// Waits for any or all the elements in the specified array to receive a signal.
//
// Parameters:
// waitHandles:
// An array of type System.IntPtr that contains the native operating system
// handles.
//
// waitAll:
// true to wait for all handles; false to wait for any handle.
//
// millisecondsTimeout:
// The number of milliseconds to wait, or System.Threading.Timeout.Infinite
// (-1) to wait indefinitely.
//
// Returns:
// The array index of the object that satisfied the wait.
[PrePrepareMethod]
[CLSCompliant(false)]
public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout);
//
// Summary:
// Helper function that waits for any or all the elements in the specified array
// to receive a signal.
//
// Parameters:
// waitHandles:
// An array of type System.IntPtr that contains the native operating system
// handles.
//
// waitAll:
// true to wait for all handles; false to wait for any handle.
//
// millisecondsTimeout:
// The number of milliseconds to wait, or System.Threading.Timeout.Infinite
// (-1) to wait indefinitely.
//
// Returns:
// The array index of the object that satisfied the wait.
[ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)]
[PrePrepareMethod]
[CLSCompliant(false)]
protected static int WaitHelper(IntPtr[] waitHandles,
bool waitAll, int millisecondsTimeout);
}
下面是Post方法的注释:
//
// Summary:
// When overridden in a derived class, dispatches an asynchronous message to
// a synchronization context.
//
// Parameters:
// d:
// The System.Threading.SendOrPostCallback delegate to call.
//
// state:
// The object passed to the delegate.
public virtual void Post(SendOrPostCallback d, object state);
这里的关键字是asynchronous,这意味着Post将不会等委托方法的执行完成。Post将会对委托的代码执行然后忘记。这同时也意味着你不能像我们之前用Send方法一样捕获异常。假设有一个异常被抛出了,它将在UI线程捕获;如果不处理就会停掉UI线程。
不管怎么说,Post也好,Send也好,都将在当前的线程里执行委托。用Post替换掉Send,你就可以得到在UI线程里执行的正确的线程id。
这样的话,我就可以用SynchronizationContext来进行任意我想的线程同步了么?不能!
现在,你可能会在任何线程中用SynchronizationContext。但是,你很快就会发现不是在每次用SynchronizationContext.Current的时候都会有SynchronizationContext实例,它经常会返回null。不用你说,你可以很简单地在没有Sync Context的时候创建一个。这确实很简单,但也确实没用。
让我们看一个与刚才的UI线程例子很类似的代码:
class Program
{
private static SynchronizationContext mT1 = null;
static void Main(string[] args)
{
// log the thread id
int id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Main thread is " + id);
// create a sync context for this thread
var context = new SynchronizationContext();
// set this context for this thread.
SynchronizationContext.SetSynchronizationContext(context);
// create a thread, and pass it the main sync context.
Thread t1 = new Thread(new ParameterizedThreadStart(Run1));
t1.Start(SynchronizationContext.Current);
Console.ReadLine();
}
static private void Run1(object state)
{
int id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Run1 Thread ID: " + id);
// grab the sync context that main has set
var context = state as SynchronizationContext;
// call the sync context of main, expecting
// the following code to run on the main thread
// but it will not.
context.Send(DoWork, null);
while (true)
Thread.Sleep(10000000);
}
static void DoWork(object state)
{
int id = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("DoWork Thread ID:" + id);
}
}
这个简单的控制台程序你就不用在家试了。这个程序是不会符合预期的,但同时也证明了之前说的那点。注意到我给主线程设置了一个Sync Context的对象。我创建了一个Sync Context实例,然后把它设置给当前的线程。这跟UI线程在创建form时所做的事情非常相像(不完全一样,我稍后会解释。)。然后,我创建了一个线程Run1,并把主线程的sync context对象传递给它。当我尝试去调用Send的时候,我发现Send是在Run1线程里被调用而不是如我们期待的一样在主线程调用。下面是输出:
Main thread is 10
Run1 Thread ID: 11
DoWork Thread ID:11
DoWork在线程11中被执行,这与线程Run1一样。没有SynchronizationContext到主线程。为什么?到底发生了什么?通过这件事情,你应该意识到生活中没有什么是免费的。线程之间不能随意的切换,他们为了达到切换还需要一个基础设施。比如,UI线程用了一个message pump在它的SynchronizationContext对象中,它使消息同步到UI线程中。
因此,UI线程拥有自己的SynchronizationContext类,这个类集成于SynchronizationContext,叫System.Windows.Forms.WindowsFormsSynchronizationContext。这个类是一个与SynchronizationContext不同的实现。UI版本重写了Post和Send方法,提供了一个关于这些方法的"message pump"版本(我尽力去找了这些类的实现,但是没有找到)。那么这个单纯的SynchronizationContext到底做了什么呢?
代码如下:(原作者卖了会萌,然后说他修改了下代码的格式,此处就不一一翻译了)
namespace System.Threading
{
using Microsoft.Win32.SafeHandles;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using System.Runtime.ConstrainedExecution;
using System.Reflection;
internal struct SynchronizationContextSwitcher : IDisposable
{
internal SynchronizationContext savedSC;
internal SynchronizationContext currSC;
internal ExecutionContext _ec;
public override bool Equals(Object obj)
{
if (obj == null || !(obj is SynchronizationContextSwitcher))
return false;
SynchronizationContextSwitcher sw = (SynchronizationContextSwitcher)obj;
return (this.savedSC == sw.savedSC &&
this.currSC == sw.currSC && this._ec == sw._ec);
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
public static bool operator ==(SynchronizationContextSwitcher c1,
SynchronizationContextSwitcher c2)
{
return c1.Equals(c2);
}
public static bool operator !=(SynchronizationContextSwitcher c1,
SynchronizationContextSwitcher c2)
{
return !c1.Equals(c2);
}
void IDisposable.Dispose()
{
Undo();
}
internal bool UndoNoThrow()
{
if (_ec == null)
{
return true;
}
try
{
Undo();
}
catch
{
return false;
}
return true;
}
public void Undo()
{
if (_ec == null)
{
return;
}
ExecutionContext executionContext =
Thread.CurrentThread.GetExecutionContextNoCreate();
if (_ec != executionContext)
{
throw new InvalidOperationException(Environment.GetResourceString(
"InvalidOperation_SwitcherCtxMismatch"));
}
if (currSC != _ec.SynchronizationContext)
{
throw new InvalidOperationException(Environment.GetResourceString(
"InvalidOperation_SwitcherCtxMismatch"));
}
BCLDebug.Assert(executionContext != null, " ExecutionContext can't be null");
// restore the Saved Sync context as current
executionContext.SynchronizationContext = savedSC;
// can't reuse this anymore
_ec = null;
}
}
public delegate void SendOrPostCallback(Object state);
[Flags]
enum SynchronizationContextProperties
{
None = 0,
RequireWaitNotification = 0x1
};
public class SynchronizationContext
{
SynchronizationContextProperties _props = SynchronizationContextProperties.None;
public SynchronizationContext()
{
}
// protected so that only the derived sync
// context class can enable these flags
protected void SetWaitNotificationRequired()
{
// Prepare the method so that it can be called
// in a reliable fashion when a wait is needed.
// This will obviously only make the Wait reliable
// if the Wait method is itself reliable. The only thing
// preparing the method here does is to ensure there
// is no failure point before the method execution begins.
RuntimeHelpers.PrepareDelegate(new WaitDelegate(this.Wait));
_props |= SynchronizationContextProperties.RequireWaitNotification;
}
public bool IsWaitNotificationRequired()
{
return ((_props &
SynchronizationContextProperties.RequireWaitNotification) != 0);
}
public virtual void Send(SendOrPostCallback d, Object state)
{
d(state);
}
public virtual void Post(SendOrPostCallback d, Object state)
{
ThreadPool.QueueUserWorkItem(new WaitCallback(d), state);
}
public virtual void OperationStarted()
{
}
public virtual void OperationCompleted()
{
}
// Method called when the CLR does a wait operation
public virtual int Wait(IntPtr[] waitHandles,
bool waitAll, int millisecondsTimeout)
{
return WaitHelper(waitHandles, waitAll, millisecondsTimeout);
}
// Static helper to which the above method
// can delegate to in order to get the default
// COM behavior.
protected static extern int WaitHelper(IntPtr[] waitHandles,
bool waitAll, int millisecondsTimeout);
// set SynchronizationContext on the current thread
public static void SetSynchronizationContext(SynchronizationContext syncContext)
{
SetSynchronizationContext(syncContext,
Thread.CurrentThread.ExecutionContext.SynchronizationContext);
}
internal static SynchronizationContextSwitcher
SetSynchronizationContext(SynchronizationContext syncContext,
SynchronizationContext prevSyncContext)
{
// get current execution context
ExecutionContext ec = Thread.CurrentThread.ExecutionContext;
// create a switcher
SynchronizationContextSwitcher scsw = new SynchronizationContextSwitcher();
RuntimeHelpers.PrepareConstrainedRegions();
try
{
// attach the switcher to the exec context
scsw._ec = ec;
// save the current sync context using the passed in value
scsw.savedSC = prevSyncContext;
// save the new sync context also
scsw.currSC = syncContext;
// update the current sync context to the new context
ec.SynchronizationContext = syncContext;
}
catch
{
// Any exception means we just restore the old SyncCtx
scsw.UndoNoThrow(); //No exception will be thrown in this Undo()
throw;
}
// return switcher
return scsw;
}
// Get the current SynchronizationContext on the current thread
public static SynchronizationContext Current
{
get
{
ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate();
if (ec != null)
return ec.SynchronizationContext;
return null;
}
}
// helper to Clone this SynchronizationContext,
public virtual SynchronizationContext CreateCopy()
{
// the CLR dummy has an empty clone function - no member data
return new SynchronizationContext();
}
private static int InvokeWaitMethodHelper(SynchronizationContext syncContext,
IntPtr[] waitHandles,
bool waitAll,
int millisecondsTimeout)
{
return syncContext.Wait(waitHandles, waitAll, millisecondsTimeout);
}
}
}
让我们看下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也是做了同样的事情,只是用了一个TreadPool来实现异步而已。我认为,这个类应当被定义为抽象的,这个默认的实现让人费解而且也没用。这也是我决定写这篇文章的原因之一。
结论
我希望你现在对这个class能够有了足够的了解,你可以弄懂怎么使用。在.net里面,我发现有两个类提供一般的同步功能。一个是Winform的线程上下文,另一个是WPF的。我相信肯定还有,但我目前只找到了这两个。这个类的默认实现没有实现从一个线程切换到另一个线程。这也是一个简单的线程在默认的情况下不能有这样效果的原因。另一方面,UI线程有"message pump"和windows的api(比如SendMessage和PostMessage),因此我确信可以封送代码到UI线程。
然而,这不是对这个类的研究的重点。你可以自己实现一个SynchronizationContext类,这也很简单。实际上,我自己也写了一个。在我的工作中,我们必须让所有基于COM的调用全部在STA的方法里运行。因此,我决定自己也一个版本的SynchronizationContext,名字叫StaSynchronizationContext。我将会在Part II部分展示给大家。
一些词语
"message pump":消息泵
后记
本篇翻译是起于在项目中要写到UI线程的回调,而又不想写Invoke,之前在别的项目中见到过这个写法,故拿来研究下,发现确实是个好东西,心动不已,so 给大家翻译下推广下此项技术。
原文链接
http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I