首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

.NET 委派深度解析(下):非同步模式、呼叫清單、異步回調實戰

     編輯:關於C語言

前言

承接上集委派基礎與事件封裝,本文深入探討多播委派呼叫清單、非同步委派模式 (APM)、輪詢 vs 回調三種異步處理方式,並以「工人 Peter、老闆 Boss、宇宙 Universe」故事串聯實戰場景。

1. 取得所有呼叫結果:GetInvocationList()

多播委派內部維護呼叫清單,預設同步串列呼叫,僅回傳最後一個結果。若需每個訂閱者回傳值:

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

2. 異步觸發並忽略結果:BeginInvoke 無回調

訂閱者處理耗時長 (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...

缺點:無法取得回傳值、無法捕捉例外、無法知道完成時機。

3. 異步觸發 + 輪詢等待:BeginInvoke + IsCompleted

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,同步等待喪失異步優勢。

4. 異步回調模式:BeginInvoke + AsyncCallback (推薦)

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();
    }
}

APM 模式關鍵規則

  1. 每個 BeginInvoke 必須配對 EndInvoke,否則洩漏資源且例外被吞沒
  2. AsyncState 傳遞委派本身,以便回調中呼叫 EndInvoke
  3. 回調執行於 ThreadPool 執行緒,UI 更新需 Control.Invoke / Dispatcher.Invoke
  4. 例外傳播:EndInvoke 會重新擲回委派中未處理的例外

現代替代方案:Task-based Asynchronous Pattern (TAP)


// .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 與底層庫中,深入理解有助於除錯與維護。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved