了解SynchronizationContext(第一部分)
SynchronizationContext - MSDN?讓人很失望
我不知道為什么评甜,但.NET Framework中這個(gè)新類的內(nèi)容真的沒(méi)有太多缴淋。MSDN文檔包含很少關(guān)于如何使用SynchronizationContext的信息艰赞。最初,我必須說(shuō),我很難理解這個(gè)新課程的原因以及如何使用它割捅。在閱讀了很多關(guān)于這個(gè)問(wèn)題之后恩掷,我終于明白了這個(gè)課程的目的以及如何使用它碧囊。我決定寫這篇文章,以幫助其他開發(fā)人員了解如何使用這個(gè)類纤怒,以及它可以和不能為你做什么糯而。
使用SynchronizationContext將代碼從一個(gè)線程組織到另一個(gè)線程
讓我們從中得到一些技術(shù)點(diǎn),以便我們可以展示如何使用這個(gè)類泊窘。SynchronizationContext允許線程與另一個(gè)線程進(jìn)行通信熄驼。假設(shè)你有兩個(gè)線程,Thread1和Thread2烘豹。說(shuō)瓜贾,Thread1正在做一些工作,然后Thread1希望在Thread2上執(zhí)行代碼携悯。一個(gè)可能的方法是詢問(wèn)Thread2的SynchronizationContext對(duì)象祭芦,給它Thread1,然后Thread1可以調(diào)用SynchronizationContext.Send來(lái)執(zhí)行Thread2上的代碼憔鬼。聽起來(lái)像魔法...嗯龟劲,有一些你應(yīng)該知道的東西。并不是每個(gè)線程都附加了一個(gè)SynchronizationContext轴或。一個(gè)線程總是有一個(gè)SynchronizationContext是UI線程昌跌。
誰(shuí)將SynchronizationContext放入U(xiǎn)I線程?好吧照雁,這是在線程上創(chuàng)建的第一個(gè)控件將SynchronizationContext放入該線程蚕愤。通常,這是創(chuàng)建的第一個(gè)表單饺蚊。我怎么知道萍诱?嗯,我試過(guò)了卸勺。
因?yàn)槲业拇a使用SynchronizationContext.Current砂沛,讓我解釋一下這個(gè)靜態(tài)屬性給我們的東西。SynchronizationContext.Current允許我們獲取一個(gè)附加到當(dāng)前線程的SynchronizationContext曙求。讓我們?cè)谶@里清楚碍庵,SynchronizationContext.Current不是每個(gè)AppDomain映企,而是每個(gè)線程的單例。這意味著當(dāng)調(diào)用SynchronizationContext.Current時(shí)静浴,兩個(gè)線程可以具有不同的SynchronizationContext實(shí)例堰氓。如果您想知道實(shí)際上下文的存儲(chǔ)位置,那么它將被存儲(chǔ)在Thread數(shù)據(jù)存儲(chǔ)中(正如我之前所說(shuō)苹享,而不是appdomain的全局內(nèi)存空間)双絮。
好的,讓我們看看在我們的UI線程中放置一個(gè)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());
}
你可以看到得问,需要注意的幾點(diǎn):
1.第一個(gè)消息框?qū)⒅甘緵](méi)有連接到線程的上下文囤攀。這是因?yàn)?NET甚至不知道在這個(gè)線程上會(huì)發(fā)生什么,并且沒(méi)有初始化此線程的同步上下文的運(yùn)行時(shí)類宫纬。
2.創(chuàng)建表單后焚挠,請(qǐng)注意上下文設(shè)置。Form類負(fù)責(zé)這個(gè)漓骚。它檢查是否存在同步上下文蝌衔,如果不存在,則將其放在那里蝌蹂。記住上下文在同一個(gè)線程上總是相同的噩斟,所以任何UI控件都可以訪問(wèn)它。這是因?yàn)樗蠻I操作必須在UI線程上運(yùn)行孤个。更具體地說(shuō)剃允,創(chuàng)建窗口的線程是可以與窗口通信的線程。在我們的例子中硼身,它是應(yīng)用程序的主線程硅急。
如何使用它?
現(xiàn)在UI線程足夠好了佳遂,給我們一個(gè)Sync
Context营袜,所以我們可以在UI線程下“運(yùn)行代碼”,我們?nèi)绾问褂盟?/p>
首先丑罪,我們真的要將代碼編入U(xiǎn)I線程嗎荚板?是。如果您運(yùn)行在UI線程以外的線程上吩屹,則無(wú)法更新UI跪另。想成為一名英雄,試試嗎煤搜?你會(huì)得到一個(gè)例外(在1.0版本中免绿,他們沒(méi)有強(qiáng)制執(zhí)行異常,它只是崩潰了應(yīng)用程序擦盾,但是在2.0版本中嘲驾,有一個(gè)令人難看的異常淌哟,在你的臉上彈出)。
為了公平起見辽故,我會(huì)說(shuō)你不必使用這個(gè)類來(lái)同步到UI線程徒仓。您可以使用InvokeRequired屬性(在每個(gè)UI控件類中),并查看是否需要編組代碼誊垢。如果您從InvokeRequired中獲得“true”掉弛,則必須使用Control.Invoke將該代碼編組到UI線程。既然這么好喂走!為什么要繼續(xù)閱讀殃饿?那么這個(gè)技術(shù)有一個(gè)問(wèn)題。你必須有一個(gè)控制才能調(diào)用Invoke芋肠。哪個(gè)UI控件無(wú)關(guān)緊要壁晒,但是在非UI線程中,您至少需要一個(gè)控件引用可用于執(zhí)行此類線程編組业栅。從設(shè)計(jì)前景來(lái)看,您從不想在您的BI層中有一個(gè)UI參考谬晕。所以碘裕,您可以在UI類別上保留所有同步操作,并確保UI負(fù)責(zé)編組自己的工作攒钳。但是帮孔,這會(huì)給UI帶來(lái)更多的責(zé)任,并且使得UI比我們想要的更聰明不撑,我必須說(shuō)文兢。對(duì)于BI來(lái)說(shuō),如果沒(méi)有引用控件或表單焕檬,就可以將代碼組織到UI線程上姆坚。
那么,怎么做呢实愚?
簡(jiǎn)單兼呵。創(chuàng)建一個(gè)線程,發(fā)送同步上下文腊敲,并讓這個(gè)線程使用同步對(duì)象來(lái)編碼到UI線程中击喂。我們來(lái)看一個(gè)例子。
在以下示例中碰辅,我有一個(gè)從工作線程填充的列表框懂昂。線程模擬計(jì)算,然后寫入U(xiǎn)I列表框没宾。用于更新UI的線程是從mToolStripButtonThreads_Click事件處理程序啟動(dòng)的凌彬。
首先沸柔,我們來(lái)看看form中的內(nèi)容:
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;
}
現(xiàn)在,我們來(lái)看一個(gè)例子:
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);
}
}
我們來(lái)看看這段代碼饿序。請(qǐng)注意勉失,我記錄每個(gè)方法的線程ID,以便稍后再查看原探。
// let's see the thread id
intid = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("mToolStripButtonThreads_Click thread: "+ id);
當(dāng)按下工具條按鈕時(shí)乱凿,將啟動(dòng)一個(gè)線程,其代理指向Run方法咽弦。但是徒蟆,請(qǐng)注意,我正在通過(guò)狀態(tài)到這個(gè)線程型型。我通過(guò)調(diào)用以下方法傳遞UI線程的同步上下文:
SynchronizationContext uiContext = SynchronizationContext.Current;
因?yàn)槲疫\(yùn)行在工具條按鈕的事件處理程序線程上段审,我知道我目前正在UI線程上運(yùn)行,通過(guò)調(diào)用SynchronizationContext.Current闹蒜,我將獲得UI線程的同步上下文寺枉。
運(yùn)行將首先從其狀態(tài)中抓取SynchronizationContext,因此它可以具有如何將代碼組織到UI線程中的知識(shí)绷落。
// grab the context from the state
SynchronizationContext uiContext = stateasSynchronizationContext;
運(yùn)行線程將1000行寫入列表框姥闪。怎么樣?那么首先它使用SynchronizationContext上的Send方法:
publicvirtualvoidSend(SendOrPostCallback d,objectstate);
調(diào)用SynchronizationContext.Send接受兩個(gè)參數(shù)砌烁,一個(gè)指向一個(gè)方法和一個(gè)狀態(tài)對(duì)象的委托筐喳。在我們的例子中...
uiContext.Send(UpdateUI,"line "+ i.ToString());
... UpdateUI是我們?yōu)榇硖峁┑闹担瑂tate包含我們要添加到列表框的字符串函喉。 UpdateUI中的代碼應(yīng)該在UI線程上運(yùn)行避归,而不是在調(diào)用線程上運(yùn)行
privatevoidUpdateUI(objectstate)
{
intid = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("UpdateUI thread:"+ id);
stringtext = stateasstring;
mListBox.Items.Add(text);
}
請(qǐng)注意,此代碼直接在UI線程上運(yùn)行管呵。沒(méi)有檢查InvokerRequired梳毙,因?yàn)槲抑浪赨I線程上,因?yàn)樗挥糜赨I?SynchronizationContext的Send方法撇寞。
我們來(lái)看看線程ID顿天,看看它是否有意義:請(qǐng)注意,此代碼直接在UI線程上運(yùn)行蔑担。沒(méi)有檢查InvokerRequired牌废,因?yàn)槲抑浪赨I線程上,因?yàn)樗挥糜赨I
SynchronizationContext的Send方法啤握。
我們來(lái)看看線程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,當(dāng)我們更新UI時(shí)懂从,請(qǐng)注意我們?cè)俅卧诰€程ID 10(UI線程)授段。所以,一切正如廣告一樣工作番甩。
錯(cuò)誤處理
非常好侵贵,我們可以將代碼編入U(xiǎn)I線程,但是當(dāng)我們編組的代碼引發(fā)異常時(shí)會(huì)發(fā)生什么缘薛?誰(shuí)負(fù)責(zé)抓住它窍育?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方法來(lái)發(fā)送一個(gè)try / catch在發(fā)送方法。
try
{
uiContext.Send(UpdateUI,"line "+ i.ToString());
}
catch(Exception e)
{
Trace.WriteLine(e.Message);
}
運(yùn)行此代碼時(shí)恕齐,我注意到異常在運(yùn)行線程中被捕獲乞娄,而不在UI線程上。這很有趣显歧,因?yàn)槟赡軙?huì)期待異常關(guān)閉UI線程仪或,考慮到?jīng)]有類在UI線程上捕獲異常。
因此士骤,Send方法正在做一點(diǎn)魔法;它以阻止的方式執(zhí)行我們的代碼溶其,并在執(zhí)行期間報(bào)告任何異常。
發(fā)送與郵寄
使用發(fā)送只是您可以用來(lái)在UI線程上編組代碼的兩種可能的方法之一敦间。還有一種叫做Post的方法。有什么不同束铭?很多廓块!
也許現(xiàn)在是更詳細(xì)地看到這個(gè)類的時(shí)候了,所以讓我們來(lái)看一下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方法的評(píng)論:
//
// 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);
//總結(jié):
//在派生類中重寫時(shí)契沫,調(diào)度異步消息
//同步上下文带猴。
//參數(shù):
// d:
//
System.Threading.SendOrPostCallback委托調(diào)用。
// state:
//傳遞給委托的對(duì)象懈万。
這里的關(guān)鍵詞是異步的拴清。這意味著郵政不會(huì)等待委托執(zhí)行完成。Post將“代替”中的執(zhí)行代碼“Fire
and Forget”会通。這也意味著您無(wú)法像使用“發(fā)送”方法捕獲異常口予。假設(shè)一個(gè)異常被拋出,它將會(huì)被UI線程所接受;脫機(jī)異常將終止UI線程涕侈。
但是沪停,郵寄或發(fā)送,代理的執(zhí)行總是在正確的線程上運(yùn)行。只需使用Post替換Send代碼木张,并且在UI線程上執(zhí)行時(shí)众辨,您仍然會(huì)獲得正確的線程ID。
所以現(xiàn)在舷礼,我可以使用SynchronizationContext來(lái)同步我想要的任何線程鹃彻,對(duì)嗎?不妻献!
此時(shí)蛛株,您可能會(huì)嘗試在任何線程中使用SynchronizationContext。但是旋奢,在使用SynchronizationContext.Current時(shí)泳挥,很快就會(huì)發(fā)現(xiàn)你的線程沒(méi)有SynchronizationContext,并且它總是返回null至朗。沒(méi)有什么大不了的屉符,如果沒(méi)有,你只要?jiǎng)?chuàng)建一個(gè)SynchronizationContext锹引。簡(jiǎn)單矗钟。但是,它沒(méi)有真正的工作嫌变。
我們來(lái)看一個(gè)類似于我們用于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);
}
}
這個(gè)簡(jiǎn)單的控制臺(tái)應(yīng)用程序是你不應(yīng)該在家里做的吨艇。這個(gè)程序不起作用,只是為了證明一點(diǎn)腾啥。注意我正在主控制臺(tái)線程上設(shè)置同步上下文东涡。我只是創(chuàng)建一個(gè)新的對(duì)象實(shí)例。然后倘待,我把它設(shè)置為我當(dāng)前的線程疮跑。這與UI線程在創(chuàng)建表單時(shí)不同(不是真的,但稍后會(huì)解釋)類似凸舵。然后祖娘,我創(chuàng)建一個(gè)線程Run1,并傳遞主線程的上下文啊奄。當(dāng)我嘗試調(diào)用發(fā)送渐苏,根據(jù)我的跟蹤,我注意到發(fā)送被調(diào)用在Run1線程菇夸,而不是主線程琼富,我們可以期待。這是輸出:
Main thread is 10
Run1 Thread ID: 11
DoWork Thread ID:11
請(qǐng)注意庄新,DoWork在線程11上執(zhí)行公黑,與Run1相同。沒(méi)有太多的SynchronizationContext進(jìn)入主線程。為什么凡蚜?這是怎么回事人断?嗯,這是你意識(shí)到?jīng)]有什么是免費(fèi)的生活中的一部分朝蜘。線程不能僅僅切換它們之間的上下文恶迈,它們必須具有內(nèi)置的基礎(chǔ)設(shè)施,以便這樣做谱醇。例如暇仲,UI線程使用消息泵,并且在其SynchronizationContext中副渴,它利用消息泵來(lái)同步到UI線程奈附。
所以,UI線程有它自己的SynchronizationContext類煮剧,但它是一個(gè)派生自SynchronizationContext的類斥滤,它被稱為System.Windows.Forms.WindowsFormsSynchronizationContext。現(xiàn)在勉盅,這個(gè)類與簡(jiǎn)單的SynchronizationContext完全不同佑颇。UI版本覆蓋了Post和Send方法,并提供了這些方法的“消息泵”版本草娜。那么簡(jiǎn)單的普通的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);
}
}
}
看看發(fā)送和郵寄的實(shí)現(xiàn)...
publicvirtualvoidSend(SendOrPostCallback d,Objectstate)
{
d(state);
}
publicvirtualvoidPost(SendOrPostCallback d,Objectstate)
{
ThreadPool.QueueUserWorkItem(newWaitCallback(d), state);
}
發(fā)送只需調(diào)用線程上的委托(不需要任何線程切換),而Post做同樣的事情宰闰,只是使用ThreadPool以異步方式執(zhí)行茬贵。在我看來(lái),這個(gè)類應(yīng)該是抽象的移袍。這個(gè)類的默認(rèn)實(shí)現(xiàn)是令人困惑和無(wú)用的闷沥。
結(jié)論
我希望你現(xiàn)在更了解這個(gè)類,你明白如何使用它咐容。在.NET中,我發(fā)現(xiàn)了兩個(gè)提供自定義同步的類蚂维。一個(gè)用于WinForms線程上下文戳粒,一個(gè)用于WPF線程上下文。我相信有更多的虫啥,但這些是我到目前為止發(fā)現(xiàn)的蔚约。我向你展示的類的默認(rèn)實(shí)現(xiàn)不會(huì)將代碼從一個(gè)線程切換到另一個(gè)線程。這只是因?yàn)槟J(rèn)情況下涂籽,線程沒(méi)有這種類型的機(jī)制苹祟。另一方面,UI線程具有消息泵和Windows
API,例如SendMessage和PostMessage树枫,我確定在將代碼編組到UI線程時(shí)使用直焙。
但是,這不應(yīng)該是這個(gè)課程的結(jié)束砂轻。你可以制作自己的SynchronizationContext奔誓,真的很簡(jiǎn)單。其實(shí)我不得不寫一個(gè)搔涝。在我的工作中厨喂,我們需要在STA線程上執(zhí)行所有基于COM的調(diào)用。但是庄呈,我們的應(yīng)用程序正在使用線程池和WCF蜕煌,并且將代碼組織到STA線程中并不簡(jiǎn)單。因此诬留,我決定將自己的SynchronizationContext版本稱為StaSynchronizationContext斜纪。