WCF 雙工回調與服務併發同樣面臨**執行緒親和性**挑戰:WinForms UI 執行緒若被服務調用阻塞,回調嘗試回同一 UI 執行緒將造成**死結**。本文從雙工計算器實例出發,系統講解 `SynchronizationContext`、`CallbackBehavior.UseSynchronizationContext`、`ISynchronizeInvoke` 三種解決路徑。
場景:WinForms Client 呼叫雙工服務 `Add(op1,op2)`,傳遞回調 `DisplayResult(result)`。服務計算完成呼叫回調。
預設行為:
不等待回調完成即返回:
// 1. ThreadPool 異步回調
public void Add(double op1, double op2) {
var callback = OperationContext.Current.GetCallbackChannel();
ThreadPool.QueueUserWorkItem(_ => callback.DisplayResult(op1 + op2));
}
// 2. OperationContract IsOneWay=true
[OperationContract(IsOneWay = true)]
void Add(double op1, double op2); // 客戶端發送即返回,不等待回應
缺點:失去回調同步保證,無法處理回調異常/回傳值。
[CallbackBehavior(UseSynchronizationContext = false)]
public class CalculateCallback : ICalculateCallback {
public static TextBox ResultPanel { get; set; }
public static SynchronizationContext SyncCtx { get; set; }
public void DisplayResult(double result) {
// 在 ThreadPool 執行緒中,手動封送至 UI 執行緒
SyncCtx.Post(_ => ResultPanel.Text = result.ToString(), null);
}
}
客戶端調用前設定:
CalculateCallback.ResultPanel = this.txtResult;
CalculateCallback.SyncCtx = SynchronizationContext.Current; // 捕獲 UI 執行緒上下文
var factory = new DuplexChannelFactory(new CalculateCallback(), "endpoint");
var proxy = factory.CreateChannel();
proxy.Add(1, 2); // UI 執行緒不再阻塞於回調
Control 實作 `ISynchronizeInvoke`,提供 `Invoke/BeginInvoke` 封送:
[CallbackBehavior(UseSynchronizationContext = false)]
public class CalculateCallback : ICalculateCallback {
public static TextBox ResultPanel { get; set; }
private delegate void DisplayDelegate(TextBox tb, double val);
public void DisplayResult(double result) {
var d = new DisplayDelegate((tb, v) => tb.Text = v.ToString());
ResultPanel.BeginInvoke(d, new object[] { ResultPanel, result });
}
}
本質相同:`WindowsFormsSynchronizationContext.Post` 內部即呼叫 `Control.BeginInvoke`。前者為統一抽象,後者為 WinForms 專用。
WinForms Host 服務,操作需更新 UI (ListBox):**預設綁定 Host UI 執行緒 → 併發序列化**。
[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Reentrant, UseSynchronizationContext = false)]
public class CalculateService : ICalculate {
public static ListBox DisplayPanel { get; set; }
public static SynchronizationContext SyncCtx { get; set; }
public void Add(double op1, double op2) {
var result = op1 + op2;
var callback = OperationContext.Current.GetCallbackChannel();
// 服務端 UI 更新封送
SyncCtx.Post(_ => DisplayPanel.Items.Add($"{op1}+{op2}={result}"), null);
// 回調 (客戶端已設 UseSynchronizationContext=false,此處直接呼叫即在 ThreadPool)
callback.DisplayResult(result);
}
}
Host 載入時設定:
CalculateService.DisplayPanel = this.listBox;
CalculateService.SyncCtx = SynchronizationContext.Current;
host.Open();
| 維度 | SynchronizationContext | ISynchronizeInvoke | 異步/One-Way |
|---|---|---|---|
| 適用範圍 | 通用 (WinForms/WPF/Console/ASP.NET) | WinForms 控制項專用 | 不需同步結果場景 | API 風格 | Post/Send (Delegate) | BeginInvoke/Invoke (Delegate) | ThreadPool/IsOneWay |
| 跨平台 | 是 (WPF DispatcherSynchronizationContext) | 否 | 是 |
| 推薦度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ (舊代碼維護) | ⭐⭐ (特定場景) |
掌握執行緒親和性控制,讓 WCF 在富用戶端與高併發伺服器雙重需求下遊刃有餘。