承接上集委派基礎與事件封裝,本文深入探討多播委派呼叫清單、非同步委派模式 (APM)、輪詢 vs 回調三種異步處理方式,並以「工人 Peter、老闆 Boss、宇宙 Universe」故事串聯實戰場景。
多播委派內部維護呼叫清單,預設同步串列呼叫,僅回傳最後一個結果。若需每個訂閱者回傳值:
public void DoWork()
{
Console.WriteLine("Worker: work completed");
if (completed != null)
{
foreach (WorkCompleted wc in completed.GetInvocationList())
{
int grade = wc(); // 同步逐一呼叫
Console.WriteLine($"Worker grade = {grade}");
}
}
}
Worker: work completed
Better... // Boss 回傳 4
Worker grade = 4
Universe pleased... // Universe 回傳 7
Worker grade = 7
訂閱者處理耗時長 (Thread.Sleep),Peter 不想等待:
public void DoWork()
{
Console.WriteLine("Worker: work completed");
if (completed != null)
{
foreach (WorkCompleted wc in completed.GetInvocationList())
{
wc.BeginInvoke(null, null); // 火後不理,執行緒池處理
}
}
}
Worker: work completed
Main: worker completed work // Peter 立即繼續
Better... // 稍後在背景執行緒輸出
Universe pleased...
缺點:無法取得回傳值、無法捕捉例外、無法知道完成時機。
Peter 想要結果,但不想阻塞主執行緒太久:
public void DoWork()
{
Console.WriteLine("Worker: work completed");
if (completed != null)
{
foreach (WorkCompleted wc in completed.GetInvocationList())
{
IAsyncResult ar = wc.BeginInvoke(null, null);
while (!ar.IsCompleted) Thread.Sleep(1); // 忙碌等待 (不建議生產環境)
int grade = wc.EndInvoke(ar);
Console.WriteLine($"Worker grade = {grade}");
}
}
}
Worker: work completed
Better...
Worker grade = 6
Universe pleased...
Worker grade = 7
Main: worker completed work
缺點:輪詢浪費 CPU,同步等待喪失異步優勢。
Peter 註冊回調,完成時自動通知:
public void DoWork()
{
Console.WriteLine("Worker: work completed");
if (completed != null)
{
foreach (WorkCompleted wc in completed.GetInvocationList())
{
wc.BeginInvoke(WorkGraded, wc); // 傳遞委派本身作為狀態
}
}
}
private void WorkGraded(IAsyncResult ar)
{
WorkCompleted wc = (WorkCompleted)ar.AsyncState;
int grade = wc.EndInvoke(ar); // 必須呼叫 EndInvoke 取得結果/傳播例外
Console.WriteLine($"Worker grade = {grade}");
}
Worker: work completed
Main: worker completed work // 立即返回
Better...
Worker grade = 6
Universe pleased...
Worker grade = 7
delegate void WorkStarted();
delegate void WorkProgressing();
delegate int WorkCompleted(); // 有回傳值
class Worker
{
public event WorkStarted Started;
public event WorkProgressing Progressing;
public event WorkCompleted Completed;
public void DoWork()
{
Console.WriteLine("Worker: work started");
Started?.Invoke();
Console.WriteLine("Worker: work progressing");
Progressing?.Invoke();
Console.WriteLine("Worker: work completed");
// 此處插入四種模式任一實作
}
// 回調模式專用
private void WorkGraded(IAsyncResult ar) { ... }
}
class Boss
{
public int WorkCompleted()
{
Thread.Sleep(3000); // 模擬耗時評估
Console.WriteLine("Better...");
return 6;
}
}
class Universe
{
public static void WorkerStartedWork() => Console.WriteLine("Universe notices worker starting work");
public static int WorkerCompletedWork()
{
Thread.Sleep(4000);
Console.WriteLine("Universe pleased with worker's work");
return 7;
}
}
class Program
{
static void Main()
{
var peter = new Worker();
var boss = new Boss();
peter.Completed += boss.WorkCompleted;
peter.Started += Universe.WorkerStartedWork;
peter.Completed += Universe.WorkerCompletedWork;
peter.DoWork();
Console.WriteLine("Main: worker completed work");
Console.ReadLine();
}
}
Control.Invoke / Dispatcher.Invoke
// .NET 4.5+ 建議使用 Task/await
public async Task DoWorkAsync()
{
Console.WriteLine("Worker: work completed");
if (completed != null)
{
var tasks = completed.GetInvocationList()
.Cast()
.Select(wc => Task.Factory.FromAsync(wc.BeginInvoke, wc.EndInvoke, null));
var grades = await Task.WhenAll(tasks);
foreach (var g in grades) Console.WriteLine($"Worker grade = {g}");
}
}
優點:可讀性高、例外處理自然、支援取消、進度報告、組合子。
| 模式 | 適用場景 | 優點 | 缺點 |
|---|---|---|---|
| 同步呼叫 | 快速、少量訂閱者 | 簡單、有序、即時結果 | 阻塞、單一回傳值 |
| GetInvocationList 同步逐一 | 需所有結果、可容忍阻塞 | 取得所有回傳值 | 依序阻塞、總耗時 = 總和 |
| BeginInvoke 無回調 | 通知類、不需結果、火後不理 | 非阻塞、簡單 | 無結果、無例外處理、無完成通知 |
| BeginInvoke + 輪詢 | 需結果、不想完整阻塞 | 可取得結果 | 浪費 CPU、程式碼複雜 |
| BeginInvoke + AsyncCallback | 需結果、真正非同步 | 非阻塞、有結果、有完成通知 | 回調地獄、需手動 EndInvoke |
| Task/await (TAP) | 現代 .NET 專案 | 最佳綜合體驗 | 需 .NET 4.5+ |
理解委派非同步模式是掌握 .NET 非同步程式設計基石,雖然現代開發多用 async/await,但 APM 仍廣泛存在於舊版 API 與底層庫中,深入理解有助於除錯與維護。