學(xué)習(xí)SynchronizationContext(I)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市故响,隨后出現(xiàn)的幾起案子傀广,更是在濱河造成了極大的恐慌,老刑警劉巖彩届,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件伪冰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡樟蠕,警方通過(guò)查閱死者的電腦和手機(jī)贮聂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)寨辩,“玉大人吓懈,你說(shuō)我怎么就攤上這事∶夷” “怎么了耻警?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)甸怕。 經(jīng)常有香客問(wèn)我甘穿,道長(zhǎng),這世上最難降的妖魔是什么梢杭? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任温兼,我火速辦了婚禮,結(jié)果婚禮上武契,老公的妹妹穿的比我還像新娘募判。我一直安慰自己荡含,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布届垫。 她就那樣靜靜地躺著释液,像睡著了一般。 火紅的嫁衣襯著肌膚如雪敦腔。 梳的紋絲不亂的頭發(fā)上均澳,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天,我揣著相機(jī)與錄音符衔,去河邊找鬼找前。 笑死,一個(gè)胖子當(dāng)著我的面吹牛判族,可吹牛的內(nèi)容都是我干的躺盛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼形帮,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼槽惫!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起辩撑,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤界斜,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后合冀,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體各薇,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年君躺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了峭判。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棕叫,死狀恐怖林螃,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情俺泣,我是刑警寧澤疗认,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布,位于F島的核電站伏钠,受9級(jí)特大地震影響横漏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜贝润,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望铝宵。 院中可真熱鬧打掘,春花似錦华畏、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至横朋,卻和暖如春仑乌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背琴锭。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工晰甚, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人决帖。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓厕九,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親地回。 傳聞我的和親對(duì)象是個(gè)殘疾皇子扁远,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容