委派的 BeginInvoke/EndInvoke 是 .NET Framework 早期非同步模式(APM)。雖然現代開發應優先使用 Task/async-await,但理解 APM 對維護舊代碼、互操作非託管 API 仍有價值。
// 定義委派
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);
}
}
| 類型 | 角色 | 關鍵成員 |
|---|---|---|
| IAsyncResult | 非同步操作狀態 | IsCompleted, AsyncWaitHandle, AsyncState, CompletedSynchronously |
| AsyncCallback | 完成回調委派 | void Callback(IAsyncResult ar) |
| AsyncWaitHandle | 等待句柄 | WaitOne(), WaitAll(), WaitAny() |
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;
}
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);
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 (BeginInvoke) | TAP (Task/async-await) |
|---|---|---|
| 可讀性 | 回調地獄、out/ref 處理複雜 | 線性代碼、自然例外處理 |
| 組合性 | 難以組合多個非同步操作 | Task.WhenAll/WhenAny、LINQ 整合 |
| 取消支援 | 無內建機制 | CancellationToken 標準化 |
| 進度回報 | 需自行實作 | IProgress<T> 內建 |
| 執行緒池整合 | 自動使用 ThreadPool | 自動使用 ThreadPool,可配置 TaskScheduler |
| 除錯體驗 | 堆疊追蹤斷裂 | 完整 async 堆疊追蹤 |
| 效能 | 較多物件分配 | ValueTask、pooling 優化 |
APM 是歷史產物,新代碼請直接使用 async-await + Task。維護舊系統時,可用 Task.Factory.FromAsync 漸進式遷移。核心觀念:非同步不等於多執行緒,await 是語法糖編譯為狀態機,理解其底層有助於寫出高效能、正確的非同步代碼。