第二章:非同步程式設計簡介 🚀
- 第二章:非同步程式設計簡介 🚀
- 2.1 非同步程式設計的基本概念 🤔
- 同步 vs 非同步
- 非同步程式設計的優勢 🌟
- 2.2 C# 中的非同步程式設計模型簡介 🛠️
- 非同步程式設計模型(APM)
- 基於事件的非同步模式(EAP)
- 基於任務的非同步模式(TAP)
async
和await
關鍵字 🔑
- 示例程式碼
- 基於回撥(Callback)非同步程式設計模型(APM) 🧩
- 回撥地獄(Callback Hell)🔥
- 回撥地獄示例
- 基於事件的非同步模式(EAP) 📅
- 示例程式碼:
WebClient
的 EAP 模式 - EAP 的問題
- 示例程式碼:
- 基於任務的非同步模式(TAP)
- 使用
async/await
重寫回撥示例
- 使用
- 基於回撥(Callback)非同步程式設計模型(APM) 🧩
- 小結:為什麼選擇
async/await
? 💡
- 2.1 非同步程式設計的基本概念 🤔
非同步程式設計(Asynchronous Programming)是現代程式設計中處理 I/O 密集型任務的關鍵技術之一。與傳統的同步程式設計不同,非同步程式設計允許程式在等待耗時操作(如網路請求、檔案讀寫、資料庫查詢等)完成時,繼續執行其他任務,從而提高應用程式的響應性和效率。
在本章中,我們將介紹非同步程式設計的基本概念、C# 中的非同步程式設計模型以及常用的 async
和 await
關鍵字。我們還會探討非同步程式設計的一些常見應用場景和最佳實踐,幫助你理解如何在實際開發中使用非同步程式設計來提升效能。
2.1 非同步程式設計的基本概念 🤔
同步 vs 非同步
- 同步程式設計 🛑:在同步程式設計中,任務是順序執行的。如果某個操作是耗時的(如網路請求),程式會阻塞等待操作完成,之後才會繼續執行下一行程式碼。這種方式簡單直觀,但在處理 I/O 密集型任務時,可能會導致程式變得不響應。
- 非同步程式設計 🔄:非同步程式設計允許程式在執行耗時操作時,不必等待操作完成,而是立即返回繼續執行其他任務。耗時操作完成後,會透過回撥、事件或
Task
通知程式結果。這樣,程式不會被阻塞,能更好地利用系統資源。
非同步程式設計的優勢 🌟
- 提高效能和吞吐量:非同步程式設計避免了執行緒的阻塞,使得應用程式可以處理更多的任務。
- 提升使用者體驗:對於 GUI 應用程式,非同步程式設計能保持介面的響應性,避免“卡頓”現象。
- 更好地利用資源:非同步程式設計通常使用執行緒池來管理後臺任務,避免了頻繁建立和銷燬執行緒的開銷。
2.2 C# 中的非同步程式設計模型簡介 🛠️
C# 提供了多種方式來實現非同步程式設計,其中最常用的是基於 Task
和 async/await
的非同步程式設計模型。在此之前,C# 中還有以下非同步程式設計模型:
非同步程式設計模型(APM)
APM(Asynchronous Programming Model) 是 C# 中最早引入的非同步程式設計模式,通常使用 BeginXXX 和 EndXXX 方法來表示非同步操作。BeginXXX 方法啟動非同步操作,而 EndXXX 方法用於獲取操作結果。APM 使用 回撥 或 IAsyncResult 來處理非同步任務的完成。雖然簡單,但程式碼往往難以閱讀和維護,容易形成“回撥地獄”。
基於事件的非同步模式(EAP)
EAP(Event-based Asynchronous Pattern,EAP)是一種較早使用的非同步程式設計模式。它主要用於處理需要長時間執行的操作(例如 I/O 操作),並透過事件通知呼叫者操作的完成情況。這種模型曾經在 .NET Framework 2.0 和 3.5 中被廣泛使用,隨著 async/await
引入後,EAP 逐漸被淘汰。
基於任務的非同步模式(TAP)
C# 5.0 引入了 Task
和 async/await
,這是一種更加現代和簡潔的非同步程式設計方式,以同步編碼的方式編寫非同步程式碼。
async
和 await
關鍵字 🔑
async
:用於標記一個方法為非同步方法。非同步方法通常返回Task
或Task<T>
,表示非同步操作的結果。await
:用於等待一個非同步操作的完成,並在操作完成後繼續執行後續程式碼。await
會釋放當前執行緒,讓它去處理其他任務,避免了阻塞。
APM
和EAP
基本已經被淘汰了,在本系列文章後續也不會再過多介紹。詳情參考官網
示例程式碼
基於回撥(Callback)非同步程式設計模型(APM) 🧩
回撥 是一種簡單的非同步程式設計方式,通常透過委託或匿名方法來實現。開發者將一個方法作為引數傳遞給非同步操作,當操作完成時,呼叫此方法返回結果。
using System;
using System.Threading;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("開始非同步操作...");
PerformAsyncOperation(ResultCallback);
Console.WriteLine("主執行緒繼續執行...");
// 防止程式過早結束
Thread.Sleep(3000);
}
// 模擬非同步操作
static void PerformAsyncOperation(Action<string> callback)
{
new Thread(() =>
{
Thread.Sleep(2000); // 模擬耗時操作
callback("操作完成,結果為:42");
}).Start();
}
// 回撥方法
static void ResultCallback(string result)
{
Console.WriteLine(result);
}
}
輸出:
開始非同步操作...
主執行緒繼續執行...
操作完成,結果為:42
解釋:
PerformAsyncOperation
方法啟動了一個新執行緒,並在操作完成後呼叫回撥方法ResultCallback
。- 主執行緒不會被阻塞,能夠繼續執行其他操作。
- 回撥方法用於處理非同步操作的結果。
回撥地獄(Callback Hell)🔥
回撥地獄是指當多個非同步操作依次依賴回撥時,程式碼會形成巢狀的結構,導致難以維護和閱讀。這種問題在複雜場景中尤為常見。
回撥地獄示例
using System;
using System.Threading;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("開始非同步任務鏈...");
Step1(result1 =>
{
Console.WriteLine(result1);
Step2(result2 =>
{
Console.WriteLine(result2);
Step3(result3 =>
{
Console.WriteLine(result3);
Console.WriteLine("所有步驟完成!");
});
});
});
// 防止程式過早結束
Thread.Sleep(5000);
}
static void Step1(Action<string> callback)
{
new Thread(() => { Thread.Sleep(1000); callback("步驟 1 完成"); }).Start();
}
static void Step2(Action<string> callback)
{
new Thread(() => { Thread.Sleep(1000); callback("步驟 2 完成"); }).Start();
}
static void Step3(Action<string> callback)
{
new Thread(() => { Thread.Sleep(1000); callback("步驟 3 完成"); }).Start();
}
}
輸出:
開始非同步任務鏈...
步驟 1 完成
步驟 2 完成
步驟 3 完成
所有步驟完成!
問題:
- 巢狀的回撥導致程式碼呈現“金字塔”結構,難以閱讀和維護。
- 異常處理複雜,容易遺漏錯誤處理邏輯。
基於事件的非同步模式(EAP) 📅
EAP 是 C# 中較早的一種非同步程式設計模型,它使用 事件 來通知非同步操作的完成狀態。EAP 模式通常以 BeginXXX
和 EndXXX
命名方法,或者透過事件處理完成通知。
示例程式碼:WebClient
的 EAP 模式
using System;
using System.Net;
class Program
{
static void Main(string[] args)
{
using (WebClient client = new WebClient())
{
client.DownloadStringCompleted += OnDownloadStringCompleted;
Console.WriteLine("開始下載資料...");
client.DownloadStringAsync(new Uri("https://www.example.com"));
// 防止程式過早結束
Console.ReadLine();
}
}
// 下載完成時觸發的事件處理程式
static void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
Console.WriteLine($"下載失敗:{e.Error.Message}");
return;
}
Console.WriteLine("下載成功,資料長度:" + e.Result.Length);
}
}
輸出:
開始下載資料...
下載成功,資料長度:1270
解釋:
DownloadStringAsync
方法啟動非同步下載操作。- 當下載完成後,會觸發
DownloadStringCompleted
事件,並呼叫事件處理程式OnDownloadStringCompleted
。 e.Result
包含下載的資料結果。
EAP 的問題
- 複雜性:每個非同步操作都需要定義事件和處理程式,增加了程式碼的複雜性。
- 錯誤處理困難:異常處理需要在事件處理程式中顯式檢查
Error
屬性。 - 可維護性差:對於大量非同步操作,程式碼的結構會變得凌亂。
基於任務的非同步模式(TAP)
使用 async/await
重寫回撥示例
using System;
using System.Threading.Tasks;
class Program
{
static async Task Main(string[] args)
{
Console.WriteLine("開始非同步任務鏈...");
await Step1();
await Step2();
await Step3();
Console.WriteLine("所有步驟完成!");
}
static async Task Step1()
{
await Task.Delay(1000);
Console.WriteLine("步驟 1 完成");
}
static async Task Step2()
{
await Task.Delay(1000);
Console.WriteLine("步驟 2 完成");
}
static async Task Step3()
{
await Task.Delay(1000);
Console.WriteLine("步驟 3 完成");
}
}
優勢:
- 程式碼結構更加清晰,不再有深層巢狀。
- 異常處理可以使用標準的
try/catch
塊。 - 非同步方法的呼叫和同步方法類似,降低了編寫和理解非同步程式碼的難度。
解釋:
GetDataAsync
方法被標記為async
,表示這是一個非同步方法。await
關鍵字等待GetStringAsync
方法完成,而不會阻塞主執行緒。- 當網路請求完成後,
response
字串會返回給呼叫者,程式繼續執行。
小結:為什麼選擇 async/await
? 💡
隨著 C# 5.0 引入 async/await
關鍵字,非同步程式設計變得更加簡潔和直觀。相比於回撥和 EAP 模式,async/await
提供了更高層次的抽象,能夠以類似於同步程式碼的方式編寫非同步邏輯,極大地提升了程式碼的可讀性和維護性。