本文結合分散式任務管理系統實例,深入解析 WCF 雙工通訊實現事件廣播的核心技術難點:ConcurrencyMode 設定、PerSession 實例下的廣播機制、會話維護陷阱、非同步處理策略。適合具備 WCF 訊息交換模式基礎的進階學習者。
功能清單:
Accept() 連線並保持會話GetJobs() 獲取任務列表AddJob() 新增任務 → 服務端廣播「新增完成」事件Do(jobName) 執行任務 → 服務端廣播「開始/結束」事件專案結構:
| 專案 | 職責 |
|---|---|
| Jillzhang.Event.Core | 定義服務合約 (IServer)、回調合約 (ICallback)、資料合約 (Job) |
| Jillzhang.Event.Service | 服務實作,含廣播邏輯 |
| Jillzhang.Event.Host | Console 宿主 |
| Jillzhang.Event.Client / Client2 | 雙客戶端驗證廣播 |

問題:預設 ConcurrencyMode.Single 單執行緒處理請求。若回調非單向,服務端會阻塞等待客戶端回應,極易死鎖或超時。
兩種解決方案:
| 方案 | 設定 | 優缺點 |
|---|---|---|
| 回調設為 One-Way(推薦) | [OperationContract(IsOneWay=true)] + ConcurrencyMode.Single |
單執行緒安全、無阻塞、效能佳。經測試可完美支援雙工。 |
| 多執行緒模式 | ConcurrencyMode.Multiple |
允許同步回調,但需自行處理執行緒安全,複雜度高。 |
避坑:ConcurrencyMode.Reentrant 雖允許單執行緒重入,但不保證事務完整性,生產環境慎用。
挑戰:PerSession 每個會話對應一個服務實例,廣播需遍歷所有會話實例發送回調。
巧解:靜態事件 + 委派清單
// 服務類別定義靜態事件
public static event ServerEventHandler OnStatusChanged;
// 每個實例建構時訂閱
public Server() {
OnStatusChanged += InstanceHandler;
}
// 廣播實作
private void BroadcastEvent(CallbackEventArg e, ServerEventHandler temp) {
if (OnStatusChanged != null) {
foreach (ServerEventHandler handler in temp.GetInvocationList()) {
// 非同步發送,避免單一客戶端阻塞廣播
handler.BeginInvoke(this, e, new AsyncCallback(EndAsync), null);
}
}
}
關鍵點:GetInvocationList() 取得所有訂閱者委派,逐一 BeginInvoke 非同步發送。
現象:回調前幾次正常,隨後失效。根因:Accept() 後未及時呼叫業務方法,會話閒置逾時中斷。
解決:在服務合約明確標註操作角色:
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ICallback))]
public interface IServer {
// 啟動操作:IsInitiating=true, IsTerminating=false
[OperationContract(IsOneWay = true, IsInitiating = true, IsTerminating = false)]
void Accept();
// 一般操作:不啟動不終結會話
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void Do(string jobName);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void AddJob(Job job);
// 查詢操作:雙向,但不影響會話狀態
[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
List<Job> GetJobs();
}
核心邏輯:IsTerminating=false 確保操作完成後會話持續存活,防止閒置逾時自動斷開。
| 層面 | 技術選型 | 實作要點 |
|---|---|---|
| 服務端事件廣播 | 委派 BeginInvoke/EndInvoke |
參考 BroadcastEvent 實作,非同步發送避免阻塞 |
| 客戶端耗時操作 | BackgroundWorker |
UI 執行緒安全回報進度/結果,支援取消 |
| 任務執行 | 獨立執行緒/執行緒池 | 服務端 Do() 內啟動背景執行緒執行任務,主執行緒立即返回 |
// ICallback.cs - 客戶端需實作
[ServiceContract]
public interface ICallback {
[OperationContract(IsOneWay = true)]
void OnJobAdded(Job job);
[OperationContract(IsOneWay = true)]
void OnJobStarted(string jobName);
[OperationContract(IsOneWay = true)]
void OnJobCompleted(string jobName, bool success);
}
// IServer.cs - 服務端合約
[ServiceContract(SessionMode = SessionMode.Required, CallbackContract = typeof(ICallback))]
public interface IServer {
[OperationContract(IsOneWay = true, IsInitiating = true, IsTerminating = false)]
void Accept();
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void Do(string jobName);
[OperationContract(IsOneWay = true, IsInitiating = false, IsTerminating = false)]
void AddJob(Job job);
[OperationContract(IsOneWay = false, IsInitiating = false, IsTerminating = false)]
List<Job> GetJobs();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Single)]
public class Server : IServer {
// 靜態事件實現跨實例廣播
public static event ServerEventHandler OnStatusChanged;
public Server() {
OnStatusChanged += InstanceHandler;
}
public void Accept() { /* 會話建立,無邏輯 */ }
public void AddJob(Job job) {
// 業務邏輯...
BroadcastEvent(new CallbackEventArg(JobEventType.Added, job), OnStatusChanged);
}
public void Do(string jobName) {
// 非同步執行任務
ThreadPool.QueueUserWorkItem(_ => {
BroadcastEvent(new CallbackEventArg(JobEventType.Started, jobName), OnStatusChanged);
// 執行任務...
BroadcastEvent(new CallbackEventArg(JobEventType.Completed, jobName), OnStatusChanged);
});
}
// 廣播與非同步回調實作參考上文
}
BeginInvoke 群發IsInitiating/IsTerminating 精準掌控會話啟動/終結