第二章:C#異步編程簡介
第二章:異步編程簡介 ??
異步編程(Asynchronous Programming)是現代編程中處理 I/O 密集型任務的關鍵技術之一。與傳統(tǒng)的同步編程不同,異步編程允許程序在等待耗時操作(如網絡請求、文件讀寫、數據庫查詢等)完成時,繼續(xù)執(zhí)行其他任務,從而提高應用程序的響應性和效率。
在本章中,我們將介紹異步編程的基本概念、C# 中的異步編程模型以及常用的 async 和 await 關鍵字。我們還會探討異步編程的一些常見應用場景和最佳實踐,幫助你理解如何在實際開發(fā)中使用異步編程來提升性能。
2.1 異步編程的基本概念 ??
同步 vs 異步
- 同步編程 ??:在同步編程中,任務是順序執(zhí)行的。如果某個操作是耗時的(如網絡請求),程序會阻塞等待操作完成,之后才會繼續(xù)執(zhí)行下一行代碼。這種方式簡單直觀,但在處理 I/O 密集型任務時,可能會導致程序變得不響應。
- 異步編程 ??:異步編程允許程序在執(zhí)行耗時操作時,不必等待操作完成,而是立即返回繼續(xù)執(zhí)行其他任務。耗時操作完成后,會通過回調、事件或
Task通知程序結果。這樣,程序不會被阻塞,能更好地利用系統(tǒng)資源。
異步編程的優(yōu)勢 ??
- 提高性能和吞吐量:異步編程避免了線程的阻塞,使得應用程序可以處理更多的任務。
- 提升用戶體驗:對于 GUI 應用程序,異步編程能保持界面的響應性,避免“卡頓”現象。
- 更好地利用資源:異步編程通常使用線程池來管理后臺任務,避免了頻繁創(chuàng)建和銷毀線程的開銷。
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:用于等待一個異步操作的完成,并在操作完成后繼續(xù)執(zhí)行后續(xù)代碼。await會釋放當前線程,讓它去處理其他任務,避免了阻塞。
APM和EAP基本已經被淘汰了,在本系列文章后續(xù)也不會再過多介紹。詳情參考官網
示例代碼
基于回調(Callback)異步編程模型(APM) ??
回調 是一種簡單的異步編程方式,通常通過委托或匿名方法來實現。開發(fā)者將一個方法作為參數傳遞給異步操作,當操作完成時,調用此方法返回結果。
using System;
using System.Threading;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("開始異步操作...");
PerformAsyncOperation(ResultCallback);
Console.WriteLine("主線程繼續(xù)執(zhí)行...");
// 防止程序過早結束
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);
}
}
輸出:
開始異步操作...
主線程繼續(xù)執(zhí)行...
操作完成,結果為:42
解釋:
PerformAsyncOperation方法啟動了一個新線程,并在操作完成后調用回調方法ResultCallback。- 主線程不會被阻塞,能夠繼續(xù)執(zhí)行其他操作。
- 回調方法用于處理異步操作的結果。
回調地獄(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# 中較早的一種異步編程模型,它使用 事件 來通知異步操作的完成狀態(tài)。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();
}
}
// 下載完成時觸發(fā)的事件處理程序
static void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
if (e.Error != null)
{
Console.WriteLine($"下載失敗:{e.Error.Message}");
return;
}
Console.WriteLine("下載成功,數據長度:" + e.Result.Length);
}
}
輸出:
開始下載數據...
下載成功,數據長度:1270
解釋:
DownloadStringAsync方法啟動異步下載操作。- 當下載完成后,會觸發(fā)
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 完成");
}
}
優(yōu)勢:
- 代碼結構更加清晰,不再有深層嵌套。
- 異常處理可以使用標準的
try/catch塊。 - 異步方法的調用和同步方法類似,降低了編寫和理解異步代碼的難度。
解釋:
GetDataAsync方法被標記為async,表示這是一個異步方法。await關鍵字等待GetStringAsync方法完成,而不會阻塞主線程。- 當網絡請求完成后,
response字符串會返回給調用者,程序繼續(xù)執(zhí)行。
小結:為什么選擇 async/await? ??
隨著 C# 5.0 引入 async/await 關鍵字,異步編程變得更加簡潔和直觀。相比于回調和 EAP 模式,async/await 提供了更高層次的抽象,能夠以類似于同步代碼的方式編寫異步邏輯,極大地提升了代碼的可讀性和維護性。

浙公網安備 33010602011771號