上一篇中已經講了SynchronizationContext 的一些內容,現在讓我們更加深入地去了解它!
繼上篇中的問題"在UI線程上對SynchronizationContext的使用,可以適用於其他線程呢?"
OK,我們把它放置在非UI線程上,這是你用SynchronizationContext.Current的屬性來獲取,你會發 現你得到的是null,這時候,你可能會說,既然它不存在,那麼我自己創建一個SynchronizationContext 對象,這樣就沒問題了吧!?可是,最後它並不會像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); } }
輸出的結果:
Main thread is 10 Run1 Thread ID: 11 DoWork Thread ID:11
注意上面的輸出結果,DoWork和Run1是運行在同一線程中的,SynchronizationContext並沒有把 DoWork帶入到主線程中執行,為什麼呢?!
我們可以先看SynchronizationContext的原碼(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是通過線程池來實現。
這時候你也許會奇怪,為什麼UI線程上,SynchronizationContext就發揮了不同的作用呢!其實在UI 線程中使用的並不是SynchronizationContext這個類,而是WindowsFormsSynchronizationContext這個東 東。
它重寫了Send和Post方法。至於它是如何重寫實現的,這個我也不是很了解,也沒有找到相關的文章 ,只是知道通過"消息泵"來實現的,但是細節就不清楚了,如果大家知道的話,可以告訴下我,我很想了 解下!呵呵
最後,我畫了一副圖,讓我們更加清楚地了解SynchronizationContext在UI線程和一般線程之間的不 同,