SynchronizationContext 是 .NET 提供的**抽象排程器**,讓「非 UI 執行緒」能將委派排程到「UI 執行緒」執行。每個 UI 應用 (WinForms、WPF、ASP.NET 舊版) 在建立第一個 Control/Page 時,會自動為該執行緒附加專屬的 SynchronizationContext (如 WindowsFormsSynchronizationContext、DispatcherSynchronizationContext)。
[STAThread]
static void Main()
{
var ctx1 = SynchronizationContext.Current;
Console.WriteLine(ctx1 == null ? "No context" : "Has context"); // No context
var form = new Form(); // 建立 Control 瞬間附加
var ctx2 = SynchronizationContext.Current;
Console.WriteLine(ctx2 == null ? "No context" : "Has context"); // Has context
Application.Run(form);
}
結論:**Control 建構子內部呼叫 WindowsFormsSynchronizationContext.AutoInstall() 完成附加**。
| 方案 | 原理 | 缺點 |
|---|---|---|
| Control.Invoke/BeginInvoke | Control 內部持有 Handle,透過訊息佇列排程 | 業務邏輯層**強依賴 UI 控制項參考**,違反分層架構 |
| SynchronizationContext.Post/Send | 抽象排程器,**無 UI 依賴**,僅需傳遞 Context 實例 | 需手動捕獲並傳遞 Context |
| async/await (TaskScheduler) | 編譯器自動捕獲 SynchronizationContext.Current 於 await 後恢復 | .NET 4.5+ / C# 5+ 專用,舊專案不適用 |
public partial class MainForm : Form
{
private readonly SynchronizationContext _uiContext = SynchronizationContext.Current;
private void btnStart_Click(object sender, EventArgs e)
{
var worker = new BackgroundWorker(_uiContext);
worker.Run();
}
}
public class BackgroundWorker
{
private readonly SynchronizationContext _uiContext;
public BackgroundWorker(SynchronizationContext uiContext) => _uiContext = uiContext;
public void Run()
{
Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(50); // 模擬耗時運算/IO
string msg = $"Progress: {i}%";
// 關鍵:Post 非同步排程到 UI 執行緒,不阻塞背景執行緒
_uiContext.Post(UpdateUI, msg);
}
});
}
private void UpdateUI(object state)
{
// 此處執行於 UI 執行緒,可安全操作 Control
Console.WriteLine($"[UI Thread {Thread.CurrentThread.ManagedThreadId}] {state}");
// 實際專案:((MainForm)_uiContext).UpdateProgress((string)state);
}
}
try-catch。try-catch 捕獲。// Send 版本:例外會在 Run() 的 try-catch 捕獲
_uiContext.Send(_ => throw new Exception("Boom"), null);
// Post 版本:例外在 UI 執行緒拋出,Run() 的 try-catch 捕獲不到
_uiContext.Post(_ => throw new Exception("Boom"), null);
public class TestSynchronizationContext : SynchronizationContext
{
private readonly Queue<(SendOrPostCallback, object)> _queue = new();
public override void Post(SendOrPostCallback d, object state) => _queue.Enqueue((d, state));
public override void Send(SendOrPostCallback d, object state) => d(state);
public void RunAll()
{
while (_queue.Count > 0)
{
var (d, s) = _queue.Dequeue();
d(s);
}
}
}
// 單元測試
[Test]
public void Worker_Should_Post_To_UI_Context()
{
var ctx = new TestSynchronizationContext();
var worker = new BackgroundWorker(ctx);
worker.Run();
ctx.RunAll(); // 同步執行所有排程委派
// 斷言 UI 更新邏輯已執行
}
| 坑點 | 解決方案 |
|---|---|
| 背景執行緒直接操作 Control → Cross-thread exception | 一律透過 _uiContext.Post/Send 或 Control.Invoke |
在非 UI 執行緒 (如 ThreadPool) 存取 SynchronizationContext.Current 為 null | 在 UI 執行緒**儘早捕獲**並以參數/建構子注入背景元件 |
| 大量 Post 導致 UI 訊息佇列堆積、介面卡頓 | 節流:合併更新、使用 Task.Delay 批次、或改用 Progress<T> |
| ASP.NET Core 無 SynchronizationContext | 改用 await 自然流動,或 IHttpContextAccessor 取得 HttpContext |
SynchronizationContext 是 **跨執行緒 UI 更新的解耦關鍵**:它將「排程到哪個執行緒」抽象化,讓業務邏輯層**零依賴 UI 框架**,僅需持有 Context 參考即可安全排程。配合 Post (非同步) 與 Send (同步) 靈活選擇,並能輕鬆編寫單元測試。現代 .NET (Core/5+) 建議優先使用 async/await,編譯器自動處理 Context 捕獲與恢復,程式碼更簡潔安全。