非同步不是多執行緒的同義詞。**非同步 = 啟動作業 → 立即返回 → 完成時通知**,主執行緒不阻塞。I/O 密集型(檔案、網路、資料庫)非同步能釋放執行緒回執行緒池,配合 DMA(Direct Memory Access)幾乎零 CPU 佔用;計算密集型才適合多執行緒。本文系統梳理 .NET 三代非同步模式與實戰範例。
// 同步:主執行緒卡住
reader.SyncReadFile(path);
Console.WriteLine("我這裡還有一大灘事呢"); // 必須等讀完才印
// 非同步:主執行緒繼續跑
reader.AsyncReadFile(path);
Console.WriteLine("我這裡還有一大灘事呢"); // 立即印出
DoSomething(); // 繼續做別的事


| 模式 | 年代 | 關鍵 API | 特徵 |
|---|---|---|---|
| APM (Asynchronous Programming Model) | 1.0 | BeginXxx/EndXxx, IAsyncResult | 手動管理狀態、易寫錯、回調地獄 |
| EAP (Event-based Asynchronous Pattern) | 2.0 | XxxAsync, XxxCompleted event | 元件拖拉即用、WinForms/WebForms 友善 |
| TAP (Task-based Asynchronous Pattern) | 4.0+ | Task/Task<T>, async/await | 現代標準、線性寫法、組合性強、編譯器生成狀態機 |
public void AsyncReadFile(string path) {
if (!File.Exists(path)) return;
var fs = new FileStream(path, FileMode.Open); // 不能用 using,回調時需存取
fs.BeginRead(Buffer, 0, BufferSize, AsyncReadCallback, fs);
}
private void AsyncReadCallback(IAsyncResult ar) {
var fs = (FileStream)ar.AsyncState;
fs.EndRead(ar); // 必須呼叫取得實際讀取位元組數
fs.Close();
string text = Encoding.UTF8.GetString(Buffer);
Console.WriteLine("讀取完成:" + text);
}
DoWork(背景執行緒)、ProgressChanged(UI 執行緒)、RunWorkerCompleted(UI 執行緒)
public delegate string DownloadDelegate(string url);
public IAsyncResult BeginDownload(string url) {
var d = new DownloadDelegate(DownloadSync);
return d.BeginInvoke(url, DownloadCallback, d); // 回調帶入 delegate 自身
}
private void DownloadCallback(IAsyncResult ar) {
var d = (DownloadDelegate)ar.AsyncState;
string result = d.EndInvoke(ar); // 取得回傳值
Console.WriteLine("下載完成:" + result);
}
委托編譯後會自動生成 BeginInvoke/EndInvoke,無需手寫 IAsyncResult 實作。
public async Task DownloadAsync(string url) {
using var client = new HttpClient();
return await client.GetStringAsync(url); // 非同步等待,不阻塞
}
// 呼叫端
public async void Btn_Click(object sender, EventArgs e) {
btn.Enabled = false;
string html = await DownloadAsync("https://example.com");
txtResult.Text = html;
btn.Enabled = true;
}
Task 用 .Result/.Wait() → 死結風險。using 區塊內啟動 APM 非同步(回調時資源已釋放)。async void 僅用於事件處理器,其他一律 async Task。ConfigureAwait(false) 避免捕獲 UI 同步上下文(類庫代碼必加)。從 APM 到 TAP,.NET 非同步演進史就是「如何讓開發者寫出正確、可讀、可組合的非同步代碼」的歷史。新專案請直接拥抱 async/await + Task,舊代碼逐步遷移。