SynchronizationContext -MSDN 很讓人失望
我不知道為什么,目前在.Net下關於這個類只有很少的資料。MSDN文檔也只有很少的關於如何使用SynchronizationContext的資料。最開始的時候,我不得不說我在理解為什么有這個類以及怎么使用這個類上經歷了一段困難的時間。通過閱讀大量的相關資料,我最終搞明白了這個類的目的以及它應該如何去使用。我決定寫這篇文章來幫助其他開發者理解如何使用這個類,這個類能干嘛以及它不能干嘛。
使用SynchronizationContext來封裝一段來自一個線程的代碼到另一個線程執行
讓我們先來了解一些不常見的技術點以幫助我們展示如何使用這個類。SynchronizationContext可以使一個線程與另一個線程進行通信。假設你又兩個線程,Thead1和Thread2。Thread1做某些事情,然后它想在Thread2里面執行一些代碼。一個可以實現的方式是請求Thread得到SynchronizationContext這個對象,把它給Thread1,然后Thread1可以調用SynchronizationContext的send方法在Thread2里面執行代碼。聽起來很繞口...但是這就是你需要了解的東西。不是每一個線程都有一個SynchronizationContext對象。一個總是有SynchronizationContext對象的是UI線程。
誰把SynchronizationContext對象放到UI線程里的?有沒有可以猜一下的?放棄思考了?好吧,我來告訴你答案吧。答案是在這個線程里的一個控件(control)被創建的時候會把SynchronizationContext對象放到這個線程里。一般來說,第一次會是form第一次創建的時候。我怎么知道的?好吧,我一會就給你證明。
因為我的代碼用了SynchronizationContext.Current,所以就讓我先來解釋下這個靜態屬性到底能干嘛吧。SynchronizationContext.Current可以使我們得到一個當前線程的SynchronizationContext的對象。我們必須清楚如下問題:SynchronizationContext.Current對象不是一個AppDomain一個實例的,而是每個線程一個實例。這就意味着兩個線程在調用Synchronization.Current時將會擁有他們自己的SynchronizationContext對象實例。如果你好奇這個context上下文對象怎么存儲的,那么答案就是它存儲在線程data store(就像我之前說的,不是在appDomain的全局內存空間)。
來吧,讓我們看下在我們UI線程中使用的SynchronizationContext的代碼吧。
C#
[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());
}
正如你所見,有如下幾個點需要注意:
>1. 第一個messagebox將會顯示這個線程沒有context。因為.Net都不知道這個線程就會做什么,因此沒有一個運行時類來在為這個線程初始化sync Context對象。
>2. 在form創建之后,我們就會發現context已經被設置了。Form類負責了這件事情。它會檢測sync Context是否已經有了,如果沒有,它就會給線程設置一個。記住context對象在一個線程里面是一樣的,所以任何UI控件都可以訪問它,因為所有的UI操作都必須在UI線程里面執行。再通俗點說,創建window的線程都可以與window通信。在我們的場景下,這個線程就是應用程序的主線程。
## 怎么使用它?
既然UI線程已經足夠nice了,它給了我們一個Sync Context來使我們在UI線程下執行代碼,那么我們如何寫呢?
第一步,我們要確定我們真有需要給UI線程封送的代碼么?答案肯定是“是”。如果你在一個不是UI線程的線程里面執行代碼,但你不得不去更新UI。要不要成為一個嘗試下的英雄?可惜的是,你會得到一個異常(在.net1.0都沒有引發異常,它只會讓你的程序掛掉,在.net2.0中,就會給你引發一個很惡心的異常)。
公平的說,我得說你不是必須得用這個類來與UI線程進行通信。你可以使用InvokeRequired屬性(在每個UI control里面都有)來封送你的代碼。如果你通過InvokeRequired得到一個“true”,你就可以使用Control.Invoke方法來封送代碼到UI線程。非常好!那為什么還要繼續讀我的文章呢?但是,這個技術一個問題,你必須得有一個Control你才能調用invoke方法。在UI線程里面這沒有什么,但是如果在非UI的線程里面,你如果還想封送代碼,你就只能在你的非UI線程里面增加一個control了。從設計的角度來說,在業務邏輯層應該永遠都沒有一個UI的引用。所以,你將所有的同步代碼都放到了UI類里面來讓IU保證封送。但是,這將會增加UI的功能復雜度,使UI完成比我們希望的多得多的功能。我必須說,讓一個沒有任何Control或者Form引用的業務邏輯層來負責封送代碼到UI層是一個更好的選擇。
那到底怎么做呢?
簡單來說,創建一個線程,給他sync context對象,然后就可以用這個對象給UI線程封送代碼了。讓我們看個例子吧。
在接下來的例子中,有一個list box在工作線程調用。這個線程完成了一些計算然后寫到UI的listbox里面。這個線程通過mToolStripButtonThreads_Click事件響應來更新UI。
首先,讓我們先看下form:
```C#```
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;
}
現在讓我們看這個例子:
C#
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都打印出來來方便我們一會回顧。
比如:
```C#```
// let's see the thread id
int id = Thread.CurrentThread.ManagedThreadId;
Trace.WriteLine("mToolStripButtonThreads_Click thread: " + id);
當點擊toolstrip button的時候,一個線程將會執行Run方法,需要注意的是我給這個線程傳了一個state進去。我在調用的時候傳入了UI線程的sync context對象。
C#
SynchronizationContext uiContext = SynchronizationContext.Current;
因為我在toolstrip button的事件響應中執行,所以我知道我在UI線程中。通過調用SynchronizationContext.Current,我可以從UI線程得到sync context對象。
Run 將從它的state里面得到SynchronizationContext對象,這樣它就有了像UI線程封送的代碼的能力。
```C#```
// grab the context from the state
SynchronizationContext uiContext = state as SynchronizationContext;
Run方法將會在listbox里面寫1000行。怎么辦呢?需要先用SynchronizationContext中的send方法:
C#
public virtual void Send(SendOrPostCallback d, object state);
SynchronizationContext.Send方法有兩個參數,一個是指向一個方法的委托,一個是"state"對象。在我們的例子里是這樣的:
```C#```
uiContext.Send(UpdateUI, "line " + i.ToString());
UpdateUI就是我們給這個委托傳的方法,“state”是我們想給listbox增加的string。在UpdateUI中的代碼是在UI線程中執行而不是在調用的線程。
C#
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:
```C#```
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線程還是工作線程?
C#
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);
}
}
}
///
/// This method is executed on the main UI thread.
///
private void UpdateUI(object state)
{
throw new Exception("Boom");
}
我修改了UpdateUI方法來拋出一個異常:
```C#```
throw new Exception("Boom");
當然,我也同時修改了Run方法在Send方法增加了try/catch。
C#
try
{
uiContext.Send(UpdateUI, "line " + i.ToString());
}
catch (Exception e)
{
Trace.WriteLine(e.Message);
}
當執行這個代碼的時候,我發現異常在Run方法中被捕獲而不是在UI線程中。這很有趣,因為我們本以為會是UI線程由於沒有異常處理而掛掉。
綜上,Send方法施展了一個小魔法:它讓我們的代碼在別的線程執行,但是在當前線程引發異常。
## Send 還是 Post
Send只是我們可以向UI線程封送代碼的一種方式。另一種是Post。兩者之間有什么不同呢?很多!
可能現在需要更多的了解這個類的細節了,我們先來看下SynchronizationContext的接口:
```C#```
// 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方法的注釋:
C#
//
// 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。但是,你很快就會發現不是在每次用SynchronizationContext.Current的時候都會有SynchronizationContext實例,它經常會返回null。不用你說,你可以很簡單地在沒有Sync Context的時候創建一個。這確實很簡單,但也確實沒用。
讓我們看一個與剛才的UI線程例子很類似的代碼:
```C#```
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線程里被調用而不是如我們期待的一樣在主線程調用。下面是輸出:
C#
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到底做了什么呢?
代碼如下:(原作者賣了會萌,然后說他修改了下代碼的格式,此處就不一一翻譯了)
```C#```
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的實現:
C#
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