了解SynchronizationContext(第一部分)
SynchronizationContext - MSDN 让人很失望
我不知道为什么,但.NET Framework中这个新类的内容真的没有太多。MSDN文档包含很少关于如何使用SynchronizationContext的信息。最初,我必须说,我很难理解这个新课程的原因以及如何使用它。在阅读了很多关于这个问题之后,我终于明白了这个课程的目的以及如何使用它。我决定写这篇文章,以帮助其他开发人员了解如何使用这个类,以及它可以和不能为你做什么。
使用SynchronizationContext将代码从一个线程组织到另一个线程
让我们从中得到一些技术点,以便我们可以展示如何使用这个类。SynchronizationContext允许线程与另一个线程进行通信。假设你有两个线程,Thread1和Thread2。说,Thread1正在做一些工作,然后Thread1希望在Thread2上执行代码。一个可能的方法是询问Thread2的SynchronizationContext对象,给它Thread1,然后Thread1可以调用SynchronizationContext.Send来执行Thread2上的代码。听起来像魔法...嗯,有一些你应该知道的东西。并不是每个线程都附加了一个SynchronizationContext。一个线程总是有一个SynchronizationContext是UI线程。
谁将SynchronizationContext放入UI线程?好吧,这是在线程上创建的第一个控件将SynchronizationContext放入该线程。通常,这是创建的第一个表单。我怎么知道?嗯,我试过了。
因为我的代码使用SynchronizationContext.Current,让我解释一下这个静态属性给我们的东西。SynchronizationContext.Current允许我们获取一个附加到当前线程的SynchronizationContext。让我们在这里清楚,SynchronizationContext.Current不是每个AppDomain,而是每个线程的单例。这意味着当调用SynchronizationContext.Current时,两个线程可以具有不同的SynchronizationContext实例。如果您想知道实际上下文的存储位置,那么它将被存储在Thread数据存储中(正如我之前所说,而不是appdomain的全局内存空间)。
好的,让我们看看在我们的UI线程中放置一个SynchronizationContext的代码:
[STAThread]
staticvoidMain()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
// let's check the
context here
varcontext = SynchronizationContext.Current;
if(context ==null)
MessageBox.Show("No context for this thread");
else
MessageBox.Show("We got a context");
// create a form
Form1 form =newForm1();
// 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(newForm1());
}
你可以看到,需要注意的几点:
1.第一个消息框将指示没有连接到线程的上下文。这是因为.NET甚至不知道在这个线程上会发生什么,并且没有初始化此线程的同步上下文的运行时类。
2.创建表单后,请注意上下文设置。Form类负责这个。它检查是否存在同步上下文,如果不存在,则将其放在那里。记住上下文在同一个线程上总是相同的,所以任何UI控件都可以访问它。这是因为所有UI操作必须在UI线程上运行。更具体地说,创建窗口的线程是可以与窗口通信的线程。在我们的例子中,它是应用程序的主线程。
如何使用它?
现在UI线程足够好了,给我们一个Sync
Context,所以我们可以在UI线程下“运行代码”,我们如何使用它?
首先,我们真的要将代码编入UI线程吗?是。如果您运行在UI线程以外的线程上,则无法更新UI。想成为一名英雄,试试吗?你会得到一个例外(在1.0版本中,他们没有强制执行异常,它只是崩溃了应用程序,但是在2.0版本中,有一个令人难看的异常,在你的脸上弹出)。
为了公平起见,我会说你不必使用这个类来同步到UI线程。您可以使用InvokeRequired属性(在每个UI控件类中),并查看是否需要编组代码。如果您从InvokeRequired中获得“true”,则必须使用Control.Invoke将该代码编组到UI线程。既然这么好!为什么要继续阅读?那么这个技术有一个问题。你必须有一个控制才能调用Invoke。哪个UI控件无关紧要,但是在非UI线程中,您至少需要一个控件引用可用于执行此类线程编组。从设计前景来看,您从不想在您的BI层中有一个UI参考。所以,您可以在UI类别上保留所有同步操作,并确保UI负责编组自己的工作。但是,这会给UI带来更多的责任,并且使得UI比我们想要的更聪明,我必须说。对于BI来说,如果没有引用控件或表单,就可以将代码组织到UI线程上。
那么,怎么做呢?
简单。创建一个线程,发送同步上下文,并让这个线程使用同步对象来编码到UI线程中。我们来看一个例子。
在以下示例中,我有一个从工作线程填充的列表框。线程模拟计算,然后写入UI列表框。用于更新UI的线程是从mToolStripButtonThreads_Click事件处理程序启动的。
首先,我们来看看form中的内容:
privatevoidInitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources =
newSystem.ComponentModel.ComponentResourceManager(typeof(Form1));
this.mListBox =newSystem.Windows.Forms.ListBox();
this.toolStrip1 =newSystem.Windows.Forms.ToolStrip();
this.mToolStripButtonThreads =newSystem.Windows.Forms.ToolStripButton();
this.toolStrip1.SuspendLayout();
this.SuspendLayout();
//
// mListBox
//
this.mListBox.Dock = System.Windows.Forms.DockStyle.Fill;
this.mListBox.FormattingEnabled =true;
this.mListBox.Location =newSystem.Drawing.Point(0,0);
this.mListBox.Name ="mListBox";
this.mListBox.Size =newSystem.Drawing.Size(284,264);
this.mListBox.TabIndex =0;
//
// toolStrip1
//
this.toolStrip1.Items.AddRange(newSystem.Windows.Forms.ToolStripItem[] {
this.mToolStripButtonThreads});
this.toolStrip1.Location =newSystem.Drawing.Point(0,0);
this.toolStrip1.Name ="toolStrip1";
this.toolStrip1.Size =newSystem.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 =newSystem.Drawing.Size(148,22);
this.mToolStripButtonThreads.Text ="Press Here to start threads";
this.mToolStripButtonThreads.Click +=
newSystem.EventHandler(this.mToolStripButtonThreads_Click);
//
// Form1
//
this.AutoScaleDimensions =newSystem.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize =newSystem.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
privateSystem.Windows.Forms.ListBox mListBox;
privateSystem.Windows.Forms.ToolStrip toolStrip1;
privateSystem.Windows.Forms.ToolStripButton mToolStripButtonThreads;
}
现在,我们来看一个例子:
publicpartialclassForm1 : Form
{
publicForm1()
{
InitializeComponent();
}
privatevoidmToolStripButtonThreads_Click(objectsender, EventArgs e)
{
// let's see the thread id
intid = 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 =newThread(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);
}
privatevoidRun(objectstate)
{
// lets see the thread id
intid = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("Run thread: "+ id);
// grab the context from the state
SynchronizationContext uiContext = stateasSynchronizationContext;
for(inti =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());
}
}
///
///This method is executed on the main UI thread.
///
privatevoidUpdateUI(objectstate)
{
intid = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("UpdateUI thread:"+ id);
stringtext = state as string;
mListBox.Items.Add(text);
}
}
我们来看看这段代码。请注意,我记录每个方法的线程ID,以便稍后再查看。
// let's see the thread id
intid = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("mToolStripButtonThreads_Click thread: "+ id);
当按下工具条按钮时,将启动一个线程,其代理指向Run方法。但是,请注意,我正在通过状态到这个线程。我通过调用以下方法传递UI线程的同步上下文:
SynchronizationContext uiContext = SynchronizationContext.Current;
因为我运行在工具条按钮的事件处理程序线程上,我知道我目前正在UI线程上运行,通过调用SynchronizationContext.Current,我将获得UI线程的同步上下文。
运行将首先从其状态中抓取SynchronizationContext,因此它可以具有如何将代码组织到UI线程中的知识。
// grab the context from the state
SynchronizationContext uiContext = stateasSynchronizationContext;
运行线程将1000行写入列表框。怎么样?那么首先它使用SynchronizationContext上的Send方法:
publicvirtualvoidSend(SendOrPostCallback d,objectstate);
调用SynchronizationContext.Send接受两个参数,一个指向一个方法和一个状态对象的委托。在我们的例子中...
uiContext.Send(UpdateUI,"line "+ i.ToString());
... UpdateUI是我们为代理提供的值,state包含我们要添加到列表框的字符串。 UpdateUI中的代码应该在UI线程上运行,而不是在调用线程上运行
privatevoidUpdateUI(objectstate)
{
intid = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("UpdateUI thread:"+ id);
stringtext = stateasstring;
mListBox.Items.Add(text);
}
请注意,此代码直接在UI线程上运行。没有检查InvokerRequired,因为我知道它在UI线程上,因为它被用于UI SynchronizationContext的Send方法。
我们来看看线程ID,看看它是否有意义:请注意,此代码直接在UI线程上运行。没有检查InvokerRequired,因为我知道它在UI线程上,因为它被用于UI
SynchronizationContext的Send方法。
我们来看看线程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时,请注意我们再次在线程ID 10(UI线程)。所以,一切正如广告一样工作。
错误处理
非常好,我们可以将代码编入UI线程,但是当我们编组的代码引发异常时会发生什么?谁负责抓住它?UI线程或工作线程?
privatevoidRun(objectstate)
{
// let's see the thread id
intid = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("Run thread: "+ id);
// grab the context from the state
SynchronizationContext uiContext = stateasSynchronizationContext;
for(inti =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);
}
}
}
///
///This method is executed on the main UI thread.
///
privatevoidUpdateUI(objectstate)
{
thrownewException("Boom");
}
我修改了代码,以便UpdateUI方法抛出异常:
thrownewException("Boom");
另外,我修改了Run方法来发送一个try / catch在发送方法。
try
{
uiContext.Send(UpdateUI,"line "+ i.ToString());
}
catch(Exception e)
{
Trace.WriteLine(e.Message);
}
运行此代码时,我注意到异常在运行线程中被捕获,而不在UI线程上。这很有趣,因为您可能会期待异常关闭UI线程,考虑到没有类在UI线程上捕获异常。
因此,Send方法正在做一点魔法;它以阻止的方式执行我们的代码,并在执行期间报告任何异常。
发送与邮寄
使用发送只是您可以用来在UI线程上编组代码的两种可能的方法之一。还有一种叫做Post的方法。有什么不同?很多!
也许现在是更详细地看到这个类的时候了,所以让我们来看一下SynchronizationContext的界面:
// Summary:
//Provides the basic functionality for propagating a synchronization context
//in various synchronization models.
publicclassSynchronizationContext
{
// Summary:
//Creates a new instance of the System.Threading.SynchronizationContext class.
publicSynchronizationContext();
// Summary:
//Gets the synchronization context for the current thread.
//
// Returns:
//A System.Threading.SynchronizationContext object representing the current
//synchronization context.
publicstaticSynchronizationContext Current {get; }
// Summary:
//When overridden in a derived class, creates a copy of the synchronization
//context.
//
// Returns:
//A new System.Threading.SynchronizationContext object.
publicvirtualSynchronizationContext CreateCopy();
//
// Summary:
//Determines if wait notification is required.
//
// Returns:
//true if wait notification is required; otherwise, false.
publicboolIsWaitNotificationRequired();
//
// Summary:
//When overridden in a derived class, responds to the notification that an
//operation has completed.
publicvirtualvoidOperationCompleted();
//
// Summary:
//When overridden in a derived class, responds to the notification that an
//operation has started.
publicvirtualvoidOperationStarted();
//
// 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.
publicvirtualvoidPost(SendOrPostCallback d,objectstate);
//
// 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.
publicvirtualvoidSend(SendOrPostCallback d,objectstate);
//
// Summary:
//Sets the current synchronization context.
//
// Parameters:
//syncContext:
//The System.Threading.SynchronizationContext object to be set.
publicstaticvoidSetSynchronizationContext(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.
protectedvoidSetWaitNotificationRequired();
//
// 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)]
publicvirtualintWait(IntPtr[] waitHandles,boolwaitAll,intmillisecondsTimeout);
//
// 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)]
protectedstaticintWaitHelper(IntPtr[] waitHandles,
boolwaitAll,intmillisecondsTimeout);
}
注意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.
publicvirtualvoidPost(SendOrPostCallback d,objectstate);
//总结:
//在派生类中重写时,调度异步消息
//同步上下文。
//参数:
// d:
//
System.Threading.SendOrPostCallback委托调用。
// state:
//传递给委托的对象。
这里的关键词是异步的。这意味着邮政不会等待委托执行完成。Post将“代替”中的执行代码“Fire
and Forget”。这也意味着您无法像使用“发送”方法捕获异常。假设一个异常被抛出,它将会被UI线程所接受;脱机异常将终止UI线程。
但是,邮寄或发送,代理的执行总是在正确的线程上运行。只需使用Post替换Send代码,并且在UI线程上执行时,您仍然会获得正确的线程ID。
所以现在,我可以使用SynchronizationContext来同步我想要的任何线程,对吗?不!
此时,您可能会尝试在任何线程中使用SynchronizationContext。但是,在使用SynchronizationContext.Current时,很快就会发现你的线程没有SynchronizationContext,并且它总是返回null。没有什么大不了的,如果没有,你只要创建一个SynchronizationContext。简单。但是,它没有真正的工作。
我们来看一个类似于我们用于UI线程的程序:
classProgram
{
privatestaticSynchronizationContext mT1 =null;
staticvoidMain(string[] args)
{
// log the thread id
intid = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Main thread is "+ id);
// create a sync context for this thread
varcontext =newSynchronizationContext();
// set this context for this thread.
SynchronizationContext.SetSynchronizationContext(context);
// create a thread, and pass it the main sync context.
Thread t1 =newThread(newParameterizedThreadStart(Run1));
t1.Start(SynchronizationContext.Current);
Console.ReadLine();
}
staticprivatevoidRun1(objectstate)
{
intid = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Run1 Thread ID: "+ id);
// grabthe sync context that main has set
varcontext = stateasSynchronizationContext;
// 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);
}
staticvoidDoWork(objectstate)
{
intid = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("DoWork Thread ID:"+ id);
}
}
这个简单的控制台应用程序是你不应该在家里做的。这个程序不起作用,只是为了证明一点。注意我正在主控制台线程上设置同步上下文。我只是创建一个新的对象实例。然后,我把它设置为我当前的线程。这与UI线程在创建表单时不同(不是真的,但稍后会解释)类似。然后,我创建一个线程Run1,并传递主线程的上下文。当我尝试调用发送,根据我的跟踪,我注意到发送被调用在Run1线程,而不是主线程,我们可以期待。这是输出:
Main thread is 10
Run1 Thread ID: 11
DoWork Thread ID:11
请注意,DoWork在线程11上执行,与Run1相同。没有太多的SynchronizationContext进入主线程。为什么?这是怎么回事?嗯,这是你意识到没有什么是免费的生活中的一部分。线程不能仅仅切换它们之间的上下文,它们必须具有内置的基础设施,以便这样做。例如,UI线程使用消息泵,并且在其SynchronizationContext中,它利用消息泵来同步到UI线程。
所以,UI线程有它自己的SynchronizationContext类,但它是一个派生自SynchronizationContext的类,它被称为System.Windows.Forms.WindowsFormsSynchronizationContext。现在,这个类与简单的SynchronizationContext完全不同。UI版本覆盖了Post和Send方法,并提供了这些方法的“消息泵”版本。那么简单的普通的SynchronizationContext是什么呢?
namespaceSystem.Threading
{
usingMicrosoft.Win32.SafeHandles;
usingSystem.Security.Permissions;
usingSystem.Runtime.InteropServices;
usingSystem.Runtime.CompilerServices;
usingSystem.Runtime.ConstrainedExecution;
usingSystem.Reflection;
internalstructSynchronizationContextSwitcher : IDisposable
{
internalSynchronizationContext savedSC;
internalSynchronizationContext currSC;
internalExecutionContext _ec;
publicoverrideboolEquals(Objectobj)
{
if(obj ==null|| !(objisSynchronizationContextSwitcher))
returnfalse;
SynchronizationContextSwitcher sw = (SynchronizationContextSwitcher)obj;
return(this.savedSC == sw.savedSC &&
this.currSC == sw.currSC &&this._ec == sw._ec);
}
publicoverrideintGetHashCode()
{
returnToString().GetHashCode();
}
publicstaticbooloperator==(SynchronizationContextSwitcher c1,
SynchronizationContextSwitcher c2)
{
returnc1.Equals(c2);
}
publicstaticbooloperator!=(SynchronizationContextSwitcher c1,
SynchronizationContextSwitcher c2)
{
return!c1.Equals(c2);
}
voidIDisposable.Dispose()
{
Undo();
}
internalboolUndoNoThrow()
{
if(_ec==null)
{
returntrue;
}
try
{
Undo();
}
catch
{
returnfalse;
}
returntrue;
}
publicvoidUndo()
{
if(_ec==null)
{
return;
}
ExecutionContextexecutionContext =
Thread.CurrentThread.GetExecutionContextNoCreate();
if(_ec != executionContext)
{
thrownewInvalidOperationException(Environment.GetResourceString(
"InvalidOperation_SwitcherCtxMismatch"));
}
if(currSC != _ec.SynchronizationContext)
{
thrownewInvalidOperationException(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;
}
}
publicdelegatevoidSendOrPostCallback(Objectstate);
[Flags]
enumSynchronizationContextProperties
{
None =0,
RequireWaitNotification = 0x1
};
publicclassSynchronizationContext
{
SynchronizationContextProperties _props = SynchronizationContextProperties.None;
publicSynchronizationContext()
{
}
// protected so that only the derived sync
// context class can enable these flags
protectedvoidSetWaitNotificationRequired()
{
// 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(newWaitDelegate(this.Wait));
_props |= SynchronizationContextProperties.RequireWaitNotification;
}
publicboolIsWaitNotificationRequired()
{
return((_props &
SynchronizationContextProperties.RequireWaitNotification) !=0);
}
publicvirtualvoidSend(SendOrPostCallback d,Objectstate)
{
d(state);
}
publicvirtualvoidPost(SendOrPostCallback d,Objectstate)
{
ThreadPool.QueueUserWorkItem(newWaitCallback(d), state);
}
publicvirtualvoidOperationStarted()
{
}
publicvirtualvoidOperationCompleted()
{
}
// Method called when the CLR does a wait operation
publicvirtualintWait(IntPtr[] waitHandles,
boolwaitAll,intmillisecondsTimeout)
{
returnWaitHelper(waitHandles, waitAll, millisecondsTimeout);
}
// Static helper to which the above method
// can delegate to in order to get the default
// COM behavior.
protectedstaticexternintWaitHelper(IntPtr[] waitHandles,
boolwaitAll,intmillisecondsTimeout);
// set SynchronizationContext on the current thread
publicstaticvoidSetSynchronizationContext(SynchronizationContext syncContext)
{
SetSynchronizationContext(syncContext,
Thread.CurrentThread.ExecutionContext.SynchronizationContext);
}
internalstaticSynchronizationContextSwitcher
SetSynchronizationContext(SynchronizationContext syncContext,
SynchronizationContext prevSyncContext)
{
// get current execution context
ExecutionContext ec = Thread.CurrentThread.ExecutionContext;
// create a switcher
SynchronizationContextSwitcher scsw =newSynchronizationContextSwitcher();
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
returnscsw;
}
// Get the current SynchronizationContext on the current thread
publicstaticSynchronizationContext Current
{
get
{
ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate();
if(ec !=null)
returnec.SynchronizationContext;
returnnull;
}
}
// helper to Clone this SynchronizationContext,
publicvirtualSynchronizationContext CreateCopy()
{
// the CLR dummy has an empty clone function - no member data
returnnewSynchronizationContext();
}
privatestaticintInvokeWaitMethodHelper(SynchronizationContext syncContext,
IntPtr[] waitHandles,
boolwaitAll,
intmillisecondsTimeout)
{
returnsyncContext.Wait(waitHandles, waitAll, millisecondsTimeout);
}
}
}
看看发送和邮寄的实现...
publicvirtualvoidSend(SendOrPostCallback d,Objectstate)
{
d(state);
}
publicvirtualvoidPost(SendOrPostCallback d,Objectstate)
{
ThreadPool.QueueUserWorkItem(newWaitCallback(d), state);
}
发送只需调用线程上的委托(不需要任何线程切换),而Post做同样的事情,只是使用ThreadPool以异步方式执行。在我看来,这个类应该是抽象的。这个类的默认实现是令人困惑和无用的。
结论
我希望你现在更了解这个类,你明白如何使用它。在.NET中,我发现了两个提供自定义同步的类。一个用于WinForms线程上下文,一个用于WPF线程上下文。我相信有更多的,但这些是我到目前为止发现的。我向你展示的类的默认实现不会将代码从一个线程切换到另一个线程。这只是因为默认情况下,线程没有这种类型的机制。另一方面,UI线程具有消息泵和Windows
API,例如SendMessage和PostMessage,我确定在将代码编组到UI线程时使用。
但是,这不应该是这个课程的结束。你可以制作自己的SynchronizationContext,真的很简单。其实我不得不写一个。在我的工作中,我们需要在STA线程上执行所有基于COM的调用。但是,我们的应用程序正在使用线程池和WCF,并且将代码组织到STA线程中并不简单。因此,我决定将自己的SynchronizationContext版本称为StaSynchronizationContext。