学习SynchronizationContext(I)

了解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。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容