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

C# 委派非同步呼叫完整指南:BeginInvoke/EndInvoke、IAsyncResult、現代 Task 替代

     編輯:關於C語言

前言

委派的 BeginInvoke/EndInvoke 是 .NET Framework 早期非同步模式(APM)。雖然現代開發應優先使用 Task/async-await,但理解 APM 對維護舊代碼、互操作非託管 API 仍有價值。

委派非同步模式 (APM) 核心機制


// 定義委派
public delegate int CalculateDelegate(int x, int y, out int result);

// 執行階段自動生成:
// public IAsyncResult BeginInvoke(int x, int y, out int result, AsyncCallback callback, object state);
// public int EndInvoke(out int result, IAsyncResult asyncResult);

完整範例:四種等待完成方式


using System;
using System.Threading;

public class AsyncDelegateDemo
{
    // 耗時運算模擬
    public static int Add(int a, int b, out int result)
    {
        Thread.Sleep(3000); // 模擬 3 秒運算
        return result = a + b;
    }

    public delegate int AddDelegate(int a, int b, out int result);

    public static void Demo1_BlockingEndInvoke()
    {
        Console.WriteLine("[1] 直接 EndInvoke 阻塞等待");
        AddDelegate del = Add;
        int result;
        IAsyncResult ar = del.BeginInvoke(10, 20, out result, null, null);
        
        Console.WriteLine("主執行緒繼續做其他事...");
        Thread.Sleep(500);
        
        int sum = del.EndInvoke(out result, ar); // 這裡阻塞直到完成
        Console.WriteLine($"結果: {sum}, out參數: {result}");
    }

    public static void Demo2_WaitHandle()
    {
        Console.WriteLine("\n[2] 使用 AsyncWaitHandle.WaitOne()");
        AddDelegate del = Add;
        int result;
        IAsyncResult ar = del.BeginInvoke(6, 42, out result, null, null);
        
        Console.WriteLine("主執行緒做其他工作...");
        for (int i = 0; i < 15; i++)
        {
            Console.Write(".");
            Thread.Sleep(200);
        }
        
        // 等待完成信號
        ar.AsyncWaitHandle.WaitOne();
        Console.WriteLine("\n完成信號觸發");
        
        int sum = del.EndInvoke(out result, ar);
        Console.WriteLine($"結果: {sum}");
        
        ar.AsyncWaitHandle.Close(); // 釋放 WaitHandle
    }

    public static void Demo3_Polling()
    {
        Console.WriteLine("\n[3] 輪詢 IsCompleted");
        AddDelegate del = Add;
        int result;
        IAsyncResult ar = del.BeginInvoke(100, 200, out result, null, null);
        
        while (!ar.IsCompleted)
        {
            Console.Write("P");
            Thread.Sleep(500);
        }
        Console.WriteLine("\n輪詢偵測到完成");
        
        int sum = del.EndInvoke(out result, ar);
        Console.WriteLine($"結果: {sum}");
    }

    public static void Demo4_Callback()
    {
        Console.WriteLine("\n[4] 非同步回調 (AsyncCallback)");
        AddDelegate del = Add;
        int result;
        
        // 回調在 ThreadPool 執行緒上執行
        AsyncCallback callback = ar => 
        {
            int sum = del.EndInvoke(out result, ar);
            Console.WriteLine($"\n[回調執行緒 {Thread.CurrentThread.ManagedThreadId}] 結果: {sum}, out: {result}");
        };
        
        IAsyncResult ar = del.BeginInvoke(999, 1, out result, callback, "custom state");
        Console.WriteLine("主執行緒立即返回,等待回調...");
        
        // 這裡需保持主執行緒存活以觀察回調
        Thread.Sleep(4000);
    }
}

APM 關鍵類別與介面

類型角色關鍵成員
IAsyncResult非同步操作狀態IsCompleted, AsyncWaitHandle, AsyncState, CompletedSynchronously
AsyncCallback完成回調委派void Callback(IAsyncResult ar)
AsyncWaitHandle等待句柄WaitOne(), WaitAll(), WaitAny()

APM 進階主題

傳遞狀態物件


IAsyncResult ar = del.BeginInvoke(1, 2, out _, callback, new { UserId = 123, RequestId = Guid.NewGuid() });

// 回調中取得
void Callback(IAsyncResult ar)
{
    var state = (dynamic)ar.AsyncState;
    Console.WriteLine($"User: {state.UserId}, Request: {state.RequestId}");
}

同步完成優化


IAsyncResult ar = del.BeginInvoke(1, 2, out _, null, null);
if (ar.CompletedSynchronously)
{
    // 運算極快在同步完成,直接 EndInvoke 不阻塞
    int result = del.EndInvoke(out _, ar);
}
else
{
    // 真正非同步,稍後處理
}

例外處理傳播


try
{
    int result = del.EndInvoke(out _, ar); // 這裡會重擲背景執行緒的例外
}
catch (TargetInvocationException tie)
{
    // 真實例外在 tie.InnerException
    throw tie.InnerException;
}

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

FromAsync 包裝 APM


using System.Threading.Tasks;

public static Task AddAsync(int a, int b)
{
    AddDelegate del = Add;
    return Task.Factory.FromAsync(
        (callback, state) => del.BeginInvoke(a, b, out _, callback, state),
        ar => del.EndInvoke(out _, ar),
        null);
}

// 使用
int sum = await AddAsync(10, 20);

原生 Task 實作(推薦)


public static Task AddAsyncModern(int a, int b)
{
    return Task.Run(() => 
    {
        Thread.Sleep(3000); // 模擬運算
        return a + b;
    });
}

// 進階:支援取消、進度回報
public static async Task AddWithProgressAsync(int a, int b, IProgress progress, CancellationToken ct)
{
    for (int i = 0; i <= 100; i += 10)
    {
        ct.ThrowIfCancellationRequested();
        await Task.Delay(300, ct);
        progress?.Report(i);
    }
    return a + b;
}

APM vs TAP 對比

維度APM (BeginInvoke)TAP (Task/async-await)
可讀性回調地獄、out/ref 處理複雜線性代碼、自然例外處理
組合性難以組合多個非同步操作Task.WhenAll/WhenAny、LINQ 整合
取消支援無內建機制CancellationToken 標準化
進度回報需自行實作IProgress<T> 內建
執行緒池整合自動使用 ThreadPool自動使用 ThreadPool,可配置 TaskScheduler
除錯體驗堆疊追蹤斷裂完整 async 堆疊追蹤
效能較多物件分配ValueTask、pooling 優化

遷移檢查清單

  • ✅ 將 `delegate T Func(...)` 改為 `Func<..., Task>`
  • ✅ `BeginInvoke` → `Task.Run` 或 `Task.Factory.StartNew`
  • ✅ `EndInvoke` → `await task`
  • ✅ `AsyncCallback` → `ContinueWith` 或 `await` 後續代碼
  • ✅ `IAsyncResult.AsyncWaitHandle` → `task.Wait()` 或 `await task`
  • ✅ 加入 `CancellationToken` 參數支援取消
  • ✅ 移除 `out`/`ref` 參數,改用 Tuple 或自訂結果類別

總結

APM 是歷史產物,新代碼請直接使用 async-await + Task。維護舊系統時,可用 Task.Factory.FromAsync 漸進式遷移。核心觀念:非同步不等於多執行緒,await 是語法糖編譯為狀態機,理解其底層有助於寫出高效能、正確的非同步代碼。

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