我不知道爲何,目前在.Net下關於這個類只有不多的資料。MSDN文檔也只有不多的關於如何使用SynchronizationContext的資料。最開始的時候,我不得不說我在理解爲何有這個類以及怎麼使用這個類上經歷了一段困難的時間。經過閱讀大量的相關資料,我最終搞明白了這個類的目的以及它應該如何去使用。我決定寫這篇文章來幫助其餘開發者理解如何使用這個類,這個類能幹嗎以及它不能幹嗎。php
讓咱們先來了解一些不常見的技術點以幫助咱們展現如何使用這個類。SynchronizationContext可使一個線程與另外一個線程進行通訊。假設你又兩個線程,Thead1和Thread2。Thread1作某些事情,而後它想在Thread2裏面執行一些代碼。一個能夠實現的方式是請求Thread獲得SynchronizationContext這個對象,把它給Thread1,而後Thread1能夠調用SynchronizationContext的send方法在Thread2裏面執行代碼。聽起來很繞口...可是這就是你須要瞭解的東西。不是每個線程都有一個SynchronizationContext對象。一個老是有SynchronizationContext對象的是UI線程。css
誰把SynchronizationContext對象放到UI線程裏的?有沒有能夠猜一下的?放棄思考了?好吧,我來告訴你答案吧。答案是在這個線程裏的一個控件(control)被建立的時候會把SynchronizationContext對象放到這個線程裏。通常來講,第一次會是form第一次建立的時候。我怎麼知道的?好吧,我一會就給你證實。windows
由於個人代碼用了SynchronizationContext.Current,因此就讓我先來解釋下這個靜態屬性到底能幹嗎吧。SynchronizationContext.Current可使咱們獲得一個當前線程的SynchronizationContext的對象。咱們必須清楚以下問題:SynchronizationContext.Current對象不是一個AppDomain一個實例的,而是每一個線程一個實例。這就意味着兩個線程在調用Synchronization.Current時將會擁有他們本身的SynchronizationContext對象實例。若是你好奇這個context上下文對象怎麼存儲的,那麼答案就是它存儲在線程data store(就像我以前說的,不是在appDomain的全局內存空間)。api
來吧,讓咱們看下在咱們UI線程中使用的SynchronizationContext的代碼吧。app
[STAThread]
static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // let's check the context here var context = SynchronizationContext.Current; if (context == null) MessageBox.Show("No context for this thread"); else MessageBox.Show("We got a context"); // create a form Form1 form = new Form1(); // let's check it again after creating a form context = SynchronizationContext.Current; if (context == null) MessageBox.Show("No context for this thread"); else MessageBox.Show("We got a context"); if (context == null) MessageBox.Show("No context for this thread"); Application.Run(new Form1()); }
正如你所見,有以下幾個點須要注意:異步
既然UI線程已經足夠nice了,它給了咱們一個Sync Context來使咱們在UI線程下執行代碼,那麼咱們如何寫呢?async
第一步,咱們要肯定咱們真有須要給UI線程封送的代碼麼?答案確定是「是」。若是你在一個不是UI線程的線程裏面執行代碼,但你不得不去更新UI。要不要成爲一個嘗試下的英雄?惋惜的是,你會獲得一個異常(在.net1.0都沒有引起異常,它只會讓你的程序掛掉,在.net2.0中,就會給你引起一個很噁心的異常)。ide
公平的說,我得說你不是必須得用這個類來與UI線程進行通訊。你可使用InvokeRequired屬性(在每一個UI control裏面都有)來封送你的代碼。若是你經過InvokeRequired獲得一個「true」,你就可使用Control.Invoke方法來封送代碼到UI線程。很是好!那爲何還要繼續讀個人文章呢?可是,這個技術一個問題,你必須得有一個Control你才能調用invoke方法。在UI線程裏面這沒有什麼,可是若是在非UI的線程裏面,你若是還想封送代碼,你就只能在你的非UI線程裏面增長一個control了。從設計的角度來講,在業務邏輯層應該永遠都沒有一個UI的引用。因此,你將全部的同步代碼都放到了UI類裏面來讓IU保證封送。可是,這將會增長UI的功能複雜度,使UI完成比咱們但願的多得多的功能。我必須說,讓一個沒有任何Control或者Form引用的業務邏輯層來負責封送代碼到UI層是一個更好的選擇。oop
那到底怎麼作呢?ui
簡單來講,建立一個線程,給他sync context對象,而後就能夠用這個對象給UI線程封送代碼了。讓咱們看個例子吧。
在接下來的例子中,有一個list box在工做線程調用。這個線程完成了一些計算而後寫到UI的listbox裏面。這個線程經過mToolStripButtonThreads_Click事件響應來更新UI。
首先,讓咱們先看下form:
private void InitializeComponent() { System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); this.mListBox = new System.Windows.Forms.ListBox(); this.toolStrip1 = new System.Windows.Forms.ToolStrip(); this.mToolStripButtonThreads = new System.Windows.Forms.ToolStripButton(); this.toolStrip1.SuspendLayout(); this.SuspendLayout(); // // mListBox // this.mListBox.Dock = System.Windows.Forms.DockStyle.Fill; this.mListBox.FormattingEnabled = true; this.mListBox.Location = new System.Drawing.Point(0, 0); this.mListBox.Name = "mListBox"; this.mListBox.Size = new System.Drawing.Size(284, 264); this.mListBox.TabIndex = 0; // // toolStrip1 // this.toolStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { this.mToolStripButtonThreads}); this.toolStrip1.Location = new System.Drawing.Point(0, 0); this.toolStrip1.Name = "toolStrip1"; this.toolStrip1.Size = new System.Drawing.Size(284, 25); this.toolStrip1.TabIndex = 1; this.toolStrip1.Text = "toolStrip1"; // // mToolStripButtonThreads // this.mToolStripButtonThreads.DisplayStyle = System.Windows.Forms.ToolStripItemDisplayStyle.Text; this.mToolStripButtonThreads.Image = ((System.Drawing.Image) (resources.GetObject("mToolStripButtonThreads.Image"))); this.mToolStripButtonThreads.ImageTransparentColor = System.Drawing.Color.Magenta; this.mToolStripButtonThreads.Name = "mToolStripButtonThreads"; this.mToolStripButtonThreads.Size = new System.Drawing.Size(148, 22); this.mToolStripButtonThreads.Text = "Press Here to start threads"; this.mToolStripButtonThreads.Click += new System.EventHandler(this.mToolStripButtonThreads_Click); // // Form1 // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(284, 264); this.Controls.Add(this.toolStrip1); this.Controls.Add(this.mListBox); this.Name = "Form1"; this.Text = "Form1"; this.toolStrip1.ResumeLayout(false); this.toolStrip1.PerformLayout(); this.ResumeLayout(false); this.PerformLayout(); } #endregion private System.Windows.Forms.ListBox mListBox; private System.Windows.Forms.ToolStrip toolStrip1; private System.Windows.Forms.ToolStripButton mToolStripButtonThreads; }
如今讓咱們看這個例子:
public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void mToolStripButtonThreads_Click(object sender, EventArgs e) { // let's see the thread id int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id); // grab the sync context associated to this // thread (the UI thread), and save it in uiContext // note that this context is set by the UI thread // during Form creation (outside of your control) // also note, that not every thread has a sync context attached to it. SynchronizationContext uiContext = SynchronizationContext.Current; // create a thread and associate it to the run method Thread thread = new Thread(Run); // start the thread, and pass it the UI context, // so this thread will be able to update the UI // from within the thread thread.Start(uiContext); } private void Run(object state) { // lets see the thread id int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("Run thread: " + id); // grab the context from the state SynchronizationContext uiContext = state as SynchronizationContext; for (int i = 0; i < 1000; i++) { // normally you would do some code here // to grab items from the database. or some long // computation Thread.Sleep(10); // use the ui context to execute the UpdateUI method, // this insure that the UpdateUI method will run on the UI thread. uiContext.Post(UpdateUI, "line " + i.ToString()); } } /// <summary> /// This method is executed on the main UI thread. /// </summary> private void UpdateUI(object state) { int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("UpdateUI thread:" + id); string text = state as string; mListBox.Items.Add(text); } }
先瀏覽一遍這個代碼。你應該注意到我將每一個方法的線程ID都打印出來來方便咱們一會回顧。
好比:
// let's see the thread id int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id);
當點擊toolstrip button的時候,一個線程將會執行Run方法,須要注意的是我給這個線程傳了一個state進去。我在調用的時候傳入了UI線程的sync context對象。
SynchronizationContext uiContext = SynchronizationContext.Current;
由於我在toolstrip button的事件響應中執行,因此我知道我在UI線程中。經過調用SynchronizationContext.Current,我能夠從UI線程獲得sync context對象。
Run 將從它的state裏面獲得SynchronizationContext對象,這樣它就有了像UI線程封送的代碼的能力。
// grab the context from the state SynchronizationContext uiContext = state as SynchronizationContext;
Run方法將會在listbox裏面寫1000行。怎麼辦呢?須要先用SynchronizationContext中的send方法:
public virtual void Send(SendOrPostCallback d, object state);
SynchronizationContext.Send方法有兩個參數,一個是指向一個方法的委託,一個是"state"對象。在咱們的例子裏是這樣的:
uiContext.Send(UpdateUI, "line " + i.ToString());
UpdateUI就是咱們給這個委託傳的方法,「state」是咱們想給listbox增長的string。在UpdateUI中的代碼是在UI線程中執行而不是在調用的線程。
private void UpdateUI(object state) { int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("UpdateUI thread:" + id); string text = state as string; mListBox.Items.Add(text); }
注意到這個代碼直接在UI線程中執行。這裏沒有檢查InvokerRequired,由於我知道因爲使用了UI線程中的SynchronizationContext的send方法,它就會運行在UI線程中。
讓咱們來看下線程的id:
mToolStripButtonThreads_Click thread: 10 Run thread: 3 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 UpdateUI thread:10 ... (x1000 times)
上面能夠看到UI線程是10,工做線程(Run)是3,當咱們更新UI的時候,咱們又回到了線程10(UI線程)。這樣全部的事情就像咱們想的同樣。
很好,咱們已經有能力像UI線程封送代碼了,可是當咱們封送的代碼有引起異常的時候會怎麼樣?誰來捕獲它呢?UI線程仍是工做線程?
private void Run(object state) { // let's see the thread id int id = Thread.CurrentThread.ManagedThreadId; Trace.WriteLine("Run thread: " + id); // grab the context from the state SynchronizationContext uiContext = state as SynchronizationContext; for (int i = 0; i < 1000; i++) { Trace.WriteLine("Loop " + i.ToString()); // normally you would do some code here // to grab items from the database. or some long // computation Thread.Sleep(10); // use the ui context to execute the UpdateUI method, this insure that the // UpdateUI method will run on the UI thread. try { uiContext.Send(UpdateUI, "line " + i.ToString()); } catch (Exception e) { Trace.WriteLine(e.Message); } } } /// <summary> /// This method is executed on the main UI thread. /// </summary> private void UpdateUI(object state) { throw new Exception("Boom"); }
我修改了UpdateUI方法來拋出一個異常:
throw new Exception("Boom");
固然,我也同時修改了Run方法在Send方法增長了try/catch。
try { uiContext.Send(UpdateUI, "line " + i.ToString()); } catch (Exception e) { Trace.WriteLine(e.Message); }
當執行這個代碼的時候,我發現異常在Run方法中被捕獲而不是在UI線程中。這頗有趣,由於咱們本覺得會是UI線程因爲沒有異常處理而掛掉。
綜上,Send方法施展了一個小魔法:它讓咱們的代碼在別的線程執行,可是在當前線程引起異常。
Send只是咱們能夠向UI線程封送代碼的一種方式。另外一種是Post。二者之間有什麼不一樣呢?不少!可能如今須要更多的瞭解這個類的細節了,咱們先來看下SynchronizationContext的接口:
// Summary: // Provides the basic functionality for propagating a synchronization context // in various synchronization models. public class SynchronizationContext { // Summary: // Creates a new instance of the System.Threading.SynchronizationContext class. public SynchronizationContext(); // Summary: // Gets the synchronization context for the current thread. // // Returns: // A System.Threading.SynchronizationContext object representing the current // synchronization context. public static SynchronizationContext Current { get; } // Summary: // When overridden in a derived class, creates a copy of the synchronization // context. // // Returns: // A new System.Threading.SynchronizationContext object. public virtual SynchronizationContext CreateCopy(); // // Summary: // Determines if wait notification is required. // // Returns: // true if wait notification is required; otherwise, false. public bool IsWaitNotificationRequired(); // // Summary: // When overridden in a derived class, responds to the notification that an // operation has completed. public virtual void OperationCompleted(); // // Summary: // When overridden in a derived class, responds to the notification that an // operation has started. public virtual void OperationStarted(); // // Summary: // When overridden in a derived class, dispatches an asynchronous message to // a synchronization context. // // Parameters: // d: // The System.Threading.SendOrPostCallback delegate to call. // // state: // The object passed to the delegate. public virtual void Post(SendOrPostCallback d, object state); // // Summary: // When overridden in a derived class, dispatches a synchronous message to a // synchronization context. // // Parameters: // d: // The System.Threading.SendOrPostCallback delegate to call. // // state: // The object passed to the delegate. public virtual void Send(SendOrPostCallback d, object state); // // Summary: // Sets the current synchronization context. // // Parameters: // syncContext: // The System.Threading.SynchronizationContext object to be set. public static void SetSynchronizationContext(SynchronizationContext syncContext); // // Summary: // Sets notification that wait notification is required and prepares the callback // method so it can be called more reliably when a wait occurs. protected void SetWaitNotificationRequired(); // // Summary: // Waits for any or all the elements in the specified array to receive a signal. // // Parameters: // waitHandles: // An array of type System.IntPtr that contains the native operating system // handles. // // waitAll: // true to wait for all handles; false to wait for any handle. // // millisecondsTimeout: // The number of milliseconds to wait, or System.Threading.Timeout.Infinite // (-1) to wait indefinitely. // // Returns: // The array index of the object that satisfied the wait. [PrePrepareMethod] [CLSCompliant(false)] public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); // // Summary: // Helper function that waits for any or all the elements in the specified array // to receive a signal. // // Parameters: // waitHandles: // An array of type System.IntPtr that contains the native operating system // handles. // // waitAll: // true to wait for all handles; false to wait for any handle. // // millisecondsTimeout: // The number of milliseconds to wait, or System.Threading.Timeout.Infinite // (-1) to wait indefinitely. // // Returns: // The array index of the object that satisfied the wait. [ReliabilityContract(Consistency.WillNotCorruptState, Cer.MayFail)] [PrePrepareMethod] [CLSCompliant(false)] protected static int WaitHelper(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); }
下面是Post方法的註釋:
// // Summary: // When overridden in a derived class, dispatches an asynchronous message to // a synchronization context. // // Parameters: // d: // The System.Threading.SendOrPostCallback delegate to call. // // state: // The object passed to the delegate. public virtual void Post(SendOrPostCallback d, object state);
這裏的關鍵字是asynchronous,這意味着Post將不會等委託方法的執行完成。Post將會對委託的代碼執行而後忘記。這同時也意味着你不能像咱們以前用Send方法同樣捕獲異常。假設有一個異常被拋出了,它將在UI線程捕獲;若是不處理就會停掉UI線程。
無論怎麼說,Post也好,Send也好,都將在當前的線程裏執行委託。用Post替換掉Send,你就能夠獲得在UI線程裏執行的正確的線程id。
如今,你可能會在任何線程中用SynchronizationContext。可是,你很快就會發現不是在每次用SynchronizationContext.Current的時候都會有SynchronizationContext實例,它常常會返回null。不用你說,你能夠很簡單地在沒有Sync Context的時候建立一個。這確實很簡單,但也確實沒用。
讓咱們看一個與剛纔的UI線程例子很相似的代碼:
class Program { private static SynchronizationContext mT1 = null; static void Main(string[] args) { // log the thread id int id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Main thread is " + id); // create a sync context for this thread var context = new SynchronizationContext(); // set this context for this thread. SynchronizationContext.SetSynchronizationContext(context); // create a thread, and pass it the main sync context. Thread t1 = new Thread(new ParameterizedThreadStart(Run1)); t1.Start(SynchronizationContext.Current); Console.ReadLine(); } static private void Run1(object state) { int id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("Run1 Thread ID: " + id); // grab the sync context that main has set var context = state as SynchronizationContext; // call the sync context of main, expecting // the following code to run on the main thread // but it will not. context.Send(DoWork, null); while (true) Thread.Sleep(10000000); } static void DoWork(object state) { int id = Thread.CurrentThread.ManagedThreadId; Console.WriteLine("DoWork Thread ID:" + id); } }
這個簡單的控制檯程序你就不用在家試了。這個程序是不會符合預期的,但同時也證實了以前說的那點。注意到我給主線程設置了一個Sync Context的對象。我建立了一個Sync Context實例,而後把它設置給當前的線程。這跟UI線程在建立form時所作的事情很是相像(不徹底同樣,我稍後會解釋。)。而後,我建立了一個線程Run1,並把主線程的sync context對象傳遞給它。當我嘗試去調用Send的時候,我發現Send是在Run1線程裏被調用而不是如咱們期待的同樣在主線程調用。下面是輸出:
Main thread is 10 Run1 Thread ID: 11 DoWork Thread ID:11
DoWork在線程11中被執行,這與線程Run1同樣。沒有SynchronizationContext到主線程。爲何?到底發生了什麼?經過這件事情,你應該意識到生活中沒有什麼是免費的。線程之間不能隨意的切換,他們爲了達到切換還須要一個基礎設施。好比,UI線程用了一個message pump在它的SynchronizationContext對象中,它使消息同步到UI線程中。
所以,UI線程擁有本身的SynchronizationContext類,這個類集成於SynchronizationContext,叫System.Windows.Forms.WindowsFormsSynchronizationContext。這個類是一個與SynchronizationContext不一樣的實現。UI版本重寫了Post和Send方法,提供了一個關於這些方法的"message pump"版本(我盡力去找了這些類的實現,可是沒有找到)。那麼這個單純的SynchronizationContext到底作了什麼呢?
代碼以下:(原做者賣了會萌,而後說他修改了下代碼的格式,此處就不一一翻譯了)
namespace System.Threading { using Microsoft.Win32.SafeHandles; using System.Security.Permissions; using System.Runtime.InteropServices; using System.Runtime.CompilerServices; using System.Runtime.ConstrainedExecution; using System.Reflection; internal struct SynchronizationContextSwitcher : IDisposable { internal SynchronizationContext savedSC; internal SynchronizationContext currSC; internal ExecutionContext _ec; public override bool Equals(Object obj) { if (obj == null || !(obj is SynchronizationContextSwitcher)) return false; SynchronizationContextSwitcher sw = (SynchronizationContextSwitcher)obj; return (this.savedSC == sw.savedSC && this.currSC == sw.currSC && this._ec == sw._ec); } public override int GetHashCode() { return ToString().GetHashCode(); } public static bool operator ==(SynchronizationContextSwitcher c1, SynchronizationContextSwitcher c2) { return c1.Equals(c2); } public static bool operator !=(SynchronizationContextSwitcher c1, SynchronizationContextSwitcher c2) { return !c1.Equals(c2); } void IDisposable.Dispose() { Undo(); } internal bool UndoNoThrow() { if (_ec == null) { return true; } try { Undo(); } catch { return false; } return true; } public void Undo() { if (_ec == null) { return; } ExecutionContext executionContext = Thread.CurrentThread.GetExecutionContextNoCreate(); if (_ec != executionContext) { throw new InvalidOperationException(Environment.GetResourceString( "InvalidOperation_SwitcherCtxMismatch")); } if (currSC != _ec.SynchronizationContext) { throw new InvalidOperationException(Environment.GetResourceString( "InvalidOperation_SwitcherCtxMismatch")); } BCLDebug.Assert(executionContext != null, " ExecutionContext can't be null"); // restore the Saved Sync context as current executionContext.SynchronizationContext = savedSC; // can't reuse this anymore _ec = null; } } public delegate void SendOrPostCallback(Object state); [Flags] enum SynchronizationContextProperties { None = 0, RequireWaitNotification = 0x1 }; public class SynchronizationContext { SynchronizationContextProperties _props = SynchronizationContextProperties.None; public SynchronizationContext() { } // protected so that only the derived sync // context class can enable these flags protected void SetWaitNotificationRequired() { // Prepare the method so that it can be called // in a reliable fashion when a wait is needed. // This will obviously only make the Wait reliable // if the Wait method is itself reliable. The only thing // preparing the method here does is to ensure there // is no failure point before the method execution begins. RuntimeHelpers.PrepareDelegate(new WaitDelegate(this.Wait)); _props |= SynchronizationContextProperties.RequireWaitNotification; } public bool IsWaitNotificationRequired() { return ((_props & SynchronizationContextProperties.RequireWaitNotification) != 0); } public virtual void Send(SendOrPostCallback d, Object state) { d(state); } public virtual void Post(SendOrPostCallback d, Object state) { ThreadPool.QueueUserWorkItem(new WaitCallback(d), state); } public virtual void OperationStarted() { } public virtual void OperationCompleted() { } // Method called when the CLR does a wait operation public virtual int Wait(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) { return WaitHelper(waitHandles, waitAll, millisecondsTimeout); } // Static helper to which the above method // can delegate to in order to get the default // COM behavior. protected static extern int WaitHelper(IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout); // set SynchronizationContext on the current thread public static void SetSynchronizationContext(SynchronizationContext syncContext) { SetSynchronizationContext(syncContext, Thread.CurrentThread.ExecutionContext.SynchronizationContext); } internal static SynchronizationContextSwitcher SetSynchronizationContext(SynchronizationContext syncContext, SynchronizationContext prevSyncContext) { // get current execution context ExecutionContext ec = Thread.CurrentThread.ExecutionContext; // create a switcher SynchronizationContextSwitcher scsw = new SynchronizationContextSwitcher(); RuntimeHelpers.PrepareConstrainedRegions(); try { // attach the switcher to the exec context scsw._ec = ec; // save the current sync context using the passed in value scsw.savedSC = prevSyncContext; // save the new sync context also scsw.currSC = syncContext; // update the current sync context to the new context ec.SynchronizationContext = syncContext; } catch { // Any exception means we just restore the old SyncCtx scsw.UndoNoThrow(); //No exception will be thrown in this Undo() throw; } // return switcher return scsw; } // Get the current SynchronizationContext on the current thread public static SynchronizationContext Current { get { ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate(); if (ec != null) return ec.SynchronizationContext; return null; } } // helper to Clone this SynchronizationContext, public virtual SynchronizationContext CreateCopy() { // the CLR dummy has an empty clone function - no member data return new SynchronizationContext(); } private static int InvokeWaitMethodHelper(SynchronizationContext syncContext, IntPtr[] waitHandles, bool waitAll, int millisecondsTimeout) { return syncContext.Wait(waitHandles, waitAll, millisecondsTimeout); } } }
讓咱們看下Send和Post的實現:
public virtual void Send(SendOrPostCallback d, Object state) { d(state); } public virtual void Post(SendOrPostCallback d, Object state) { ThreadPool.QueueUserWorkItem(new WaitCallback(d), state); }
Send方法只是簡單的在當前線程調用了委託,而不是切換到另外一個線程,Post也是作了一樣的事情,只是用了一個TreadPool來實現異步而已。我認爲,這個類應當被定義爲抽象的,這個默認的實現讓人費解並且也沒用。這也是我決定寫這篇文章的緣由之一。
我但願你如今對這個class可以有了足夠的瞭解,你能夠弄懂怎麼使用。在.net裏面,我發現有兩個類提供通常的同步功能。一個是Winform的線程上下文,另外一個是WPF的。我相信確定還有,但我目前只找到了這兩個。這個類的默認實現沒有實現從一個線程切換到另外一個線程。這也是一個簡單的線程在默認的狀況下不能有這樣效果的緣由。另外一方面,UI線程有"message pump"和windows的api(好比SendMessage和PostMessage),所以我確信能夠封送代碼到UI線程。
然而,這不是對這個類的研究的重點。你能夠本身實現一個SynchronizationContext類,這也很簡單。實際上,我本身也寫了一個。在個人工做中,咱們必須讓全部基於COM的調用所有在STA的方法裏運行。所以,我決定本身也一個版本的SynchronizationContext,名字叫StaSynchronizationContext。我將會在Part II部分展現給你們。
"message pump":消息泵
本篇翻譯是起於在項目中要寫到UI線程的回調,而又不想寫Invoke,以前在別的項目中見到過這個寫法,故拿來研究下,發現確實是個好東西,心動不已,so 給你們翻譯下推廣下此項技術。
http://www.codeproject.com/Articles/31971/Understanding-SynchronizationContext-Part-I