上一篇提到 SynchronizationContext.Current 在 UI 執行緒可用,普通執行緒卻是 null。即使手動 SetSynchronizationContext(new SynchronizationContext()),行為也不像 UI 那樣自動封送回主執行緒。本文通過源碼分析、實驗驗證與架構圖解,徹底揭開這個核心非同步協調機制的面紗。
SynchronizationContext 的默認行為
public virtual void Send(SendOrPostCallback d, Object state) { d(state); } // 同步:當前執行緒直接執行
public virtual void Post(SendOrPostCallback d, Object state) { ThreadPool.QueueUserWorkItem(new WaitCallback(d), state); } // 非同步:丟給執行緒池
class Program {
static void Main() {
int mainId = Thread.CurrentThread.ManagedThreadId;
Console.WriteLine("Main thread: " + mainId);
var ctx = new SynchronizationContext();
SynchronizationContext.SetSynchronizationContext(ctx);
Thread t1 = new Thread(o => {
var capturedCtx = (SynchronizationContext)o;
Console.WriteLine("Worker thread: " + Thread.CurrentThread.ManagedThreadId);
capturedCtx.Send(DoWork, null); // 期望回主執行緒,實際不會
Thread.Sleep(Timeout.Infinite);
});
t1.Start(SynchronizationContext.Current);
Console.ReadLine();
}
static void DoWork(object state) {
Console.WriteLine("DoWork thread: " + Thread.CurrentThread.ManagedThreadId); // 輸出 Worker ID
}
}
輸出:
Main thread: 10 Worker thread: 11 DoWork thread: 11 <-- 並未切回主執行緒!
WindowsFormsSynchronizationContextWinForms 應用啟動時,Application.Run 內部會創建並設定 WindowsFormsSynchronizationContext (繼承自 SynchronizationContext),並掛載到當前執行緒的 ExecutionContext。
// 簡化版邏輯
public class WindowsFormsSynchronizationContext : SynchronizationContext {
private readonly Control _control; // 隱藏的訊息泵控制項
public override void Send(SendOrPostCallback d, object state) {
if (InvokeRequired) _control.Invoke(d, state); // 同步封送
else d(state);
}
public override void Post(SendOrPostCallback d, object state) {
_control.BeginInvoke(d, state); // 非同步封送
}
}
WM_COPYDATA 或內部訊息) 將委托封送到擁有訊息泵的 UI 執行緒。Control.Invoke/BeginInvoke 內部透過 PostMessage 向視窗程序投遞訊息,UI 執行緒在 Application.Run 的訊息迴圈中取出並執行。DispatcherSynchronizationContext同樣原理,但基於 Dispatcher 優先級隊列,支援更細粒度的優先級調度 (DispatcherPriority)。
| 維度 | 基類 SynchronizationContext | WindowsFormsSynchronizationContext | DispatcherSynchronizationContext |
|---|---|---|---|
| Send 行為 | 當前執行緒同步執行 | 封送到 UI 執行緒同步執行 (Invoke) | 封送到 Dispatcher 執行緒同步執行 |
| Post 行為 | ThreadPool 執行 | 封送到 UI 執行緒非同步執行 (BeginInvoke) | 封送到 Dispatcher 隊列 |
| 核心機制 | 無特殊機制 | Windows 訊息泵 + 隱藏 Control | Dispatcher 優先級隊列 |
| 適用場景 | 無特定執行緒親和性的庫代碼 | WinForms 應用 | WPF / UWP / Silverlight |
SynchronizationContext.Current 怎麼來的?
public static SynchronizationContext Current {
get {
ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate();
return ec?.SynchronizationContext;
}
}
ExecutionContext.SuppressFlow)。null。SetSynchronizationContext 實際修改的是當前執行緒 ExecutionContext 的引用,並返回 SynchronizationContextSwitcher 供 using 區塊自動恢復。
// 錯誤:可能拋 NullReferenceException
SynchronizationContext.Current.Post(...);
// 正確:捕獲並檢查
var ctx = SynchronizationContext.Current;
if (ctx != null) ctx.Post(...);
else ThreadPool.QueueUserWorkItem(...); // 回退策略
ConfigureAwait(false) 會忽略 Current
await SomeAsync().ConfigureAwait(false); // 後續代碼不捕獲當前 SynchronizationContext
// 這裡的 SynchronizationContext.Current 可能為 null 或不同實例
用於庫代碼避免死鎖、提升效能;UI 代碼通常用 ConfigureAwait(true) (預設) 保持上下文。
如前實驗所示,僅得到基類行為。若需自定義封送目標,必須繼承 SynchronizationContext 並重寫 Send/Post,內部實現自定義隊列 + 專用執行緒循環取任務執行。
public class SingleThreadSynchronizationContext : SynchronizationContext
{
private readonly BlockingCollection<Tuple<SendOrPostCallback, object>> _queue = new();
private readonly Thread _thread;
public SingleThreadSynchronizationContext()
{
_thread = new Thread(ProcessQueue) { IsBackground = true };
_thread.Start();
}
public override void Post(SendOrPostCallback d, object state) => _queue.Add(Tuple.Create(d, state));
public override void Send(SendOrPostCallback d, object state) {
var tcs = new TaskCompletionSource<object>();
_queue.Add(Tuple.Create<SendOrPostCallback, object>( (s, o) => { try { d(o); tcs.SetResult(null); } catch (Exception ex) { tcs.SetException(ex); } }, state));
tcs.Task.GetAwaiter().GetResult(); // 同步等待
}
private void ProcessQueue() {
foreach (var item in _queue.GetConsumingEnumerable())
item.Item1(item.Item2);
}
}
// 使用
var ctx = new SingleThreadSynchronizationContext();
SynchronizationContext.SetSynchronizationContext(ctx);
// 此後 Post/Send 都會在 _thread 上執行
Send 同步執行,Post 丟執行緒池。ExecutionContext,框架自動設定 UI 執行緒,普通執行緒為 null。ConfigureAwait(false) 避免捕獲上下文;UI 代碼信任預設行為;需專用執行緒時自行實現衍生類。