1. 引言
在C#中,async
和await
關(guān)鍵字是用于實(shí)現(xiàn)異步編程的強(qiáng)大工具。它們的引入極大地簡化了異步代碼的編寫,使得開發(fā)人員能夠更容易地創(chuàng)建響應(yīng)式和高性能的應(yīng)用程序。但是,要真正理解它們的工作原理,我們需要深入探討它們?cè)诘讓拥降自谧鍪裁础?/p>
2. 異步編程的基本概念
在深入async
和await
之前,我們需要理解一些基本概念:
同步執(zhí)行: 代碼按順序執(zhí)行,每個(gè)操作完成后才會(huì)進(jìn)行下一個(gè)。
異步執(zhí)行: 允許長時(shí)間運(yùn)行的操作在后臺(tái)進(jìn)行,而不阻塞主線程。
任務(wù)(Task): 表示一個(gè)異步操作。
線程: 程序執(zhí)行的最小單位。
3. Async 和 Await 的基本用法
讓我們從一個(gè)簡單的例子開始:
static async Task Main(string[] args)
{
var context = await GetWebContentAsync("http://www.baidu.com");
Console.WriteLine(context);
}
public static async Task<string> GetWebContentAsync(string url)
{
using (var client = new HttpClient())
{
string content = await client.GetStringAsync(url);
return content;
}
}
在這個(gè)例子中:
async
關(guān)鍵字標(biāo)記方法為異步方法。
方法返回Task
,表示一個(gè)最終會(huì)產(chǎn)生string的異步操作。
await
用于等待GetStringAsync
方法完成,而不阻塞線程。
4. Async 方法的轉(zhuǎn)換過程
當(dāng)你使用async
關(guān)鍵字標(biāo)記一個(gè)方法時(shí),編譯器會(huì)將其轉(zhuǎn)換為一個(gè)狀態(tài)機(jī)。這個(gè)過程大致如下:
創(chuàng)建一個(gè)實(shí)現(xiàn)了IAsyncStateMachine
接口的結(jié)構(gòu)體。
將方法體轉(zhuǎn)換為狀態(tài)機(jī)的MoveNext
方法。
每個(gè)await
表達(dá)式都成為一個(gè)可能的暫停點(diǎn),對(duì)應(yīng)狀態(tài)機(jī)中的一個(gè)狀態(tài)。
async
方法如何被分解為多個(gè)步驟,每個(gè)await
表達(dá)式對(duì)應(yīng)一個(gè)狀態(tài)。
5. Await 的工作原理
await
關(guān)鍵字的主要作用是:
檢查awaited任務(wù)是否已完成。
如果已完成,繼續(xù)執(zhí)行后續(xù)代碼。
如果未完成,注冊(cè)一個(gè)回調(diào)并返回控制權(quán)給調(diào)用者。
讓我們通過一個(gè)例子來詳細(xì)說明:
public async Task DoWorkAsync()
{
Console.WriteLine("開始工作");
await Task.Delay(1000); // 模擬耗時(shí)操作
Console.WriteLine("工作完成");
}
當(dāng)執(zhí)行到await Task.Delay(1000)
時(shí):
檢查Task.Delay(1000)
是否已完成。
如果未完成:
當(dāng)Task.Delay(1000)
完成時(shí):
6. 異步方法的執(zhí)行流程
讓我們通過一個(gè)更復(fù)雜的例子來理解異步方法的執(zhí)行流程:
static async Task Main(string[] args)
{
await MainMethodAsync();
Console.ReadKey();
}
public static async Task MainMethodAsync()
{
Console.WriteLine("1. 開始主方法");
await Method1Async();
Console.WriteLine("4. 主方法結(jié)束");
}
public static async Task Method1Async()
{
Console.WriteLine("2. 開始方法1");
await Task.Delay(1000);
Console.WriteLine("3. 方法1結(jié)束");
}
執(zhí)行流程如下:
MainMethodAsync
開始執(zhí)行,打印"1. 開始主方法"。
遇到await Method1Async()
,進(jìn)入Method1Async
。
Method1Async
打印"2. 開始方法1"。
遇到await Task.Delay(1000)
,注冊(cè)continuation并返回。
控制權(quán)回到MainMethodAsync
,但因?yàn)?code style="-webkit-tap-highlight-color: transparent; margin: 0px 2px; padding: 2px 4px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; font-size: inherit; color: rgb(233, 105, 0); line-height: inherit; border-radius: 4px; background: rgb(248, 248, 248);">Method1Async未完成,所以MainMethodAsync
也返回。
1秒后,Task.Delay
完成,觸發(fā)continuation。
Method1Async
繼續(xù)執(zhí)行,打印"3. 方法1結(jié)束"。
Method1Async
完成,觸發(fā)MainMethodAsync
的continuation。
MainMethodAsync
繼續(xù)執(zhí)行,打印"4. 主方法結(jié)束"。
7. 異常處理
async
/await
模式下的異常處理非常直觀。你可以使用常規(guī)的try/catch塊,異步方法中拋出的異常會(huì)被封裝在返回的Task中,并在await
時(shí)重新拋出。
8. 避免常見陷阱
使用async
/await
時(shí),有一些常見的陷阱需要注意:
8.1 死鎖
考慮以下代碼:
public async Task DeadlockDemoAsync()
{
await Task.Delay(1000).ConfigureAwait(false);
}
public void CallAsyncMethod()
{
DeadlockDemoAsync().Wait(); // 可能導(dǎo)致死鎖
}
當(dāng)在UI線程(或任何有同步上下文的線程)中調(diào)用CallAsyncMethod()
時(shí),會(huì)發(fā)生死鎖。
Wait()
方法會(huì)阻塞當(dāng)前線程,等待異步操作完成。
當(dāng)異步操作完成時(shí),它默認(rèn)會(huì)嘗試在原始的同步上下文(通常是UI線程)上繼續(xù)執(zhí)行。
但是原始線程已經(jīng)被Wait()
阻塞了,導(dǎo)致死鎖。
8.2 忘記await
public async Task ForgetAwaitDemoAsync()
{
DoSomethingAsync(); // 忘記await
Console.WriteLine("完成"); // 這行可能在異步操作完成之前執(zhí)行
}
始終記得在異步方法調(diào)用前使用await
。
8.3 過度使用async void
除了事件處理程序外,應(yīng)避免使用async void
方法,因?yàn)樗鼈兊漠惓ky以捕獲和處理。
public async void BadAsyncVoidMethod()
{
await Task.Delay(1000);
throw new Exception("這個(gè)異常很難被捕獲");
}
9. 高級(jí)模式
9.1 并行執(zhí)行多個(gè)任務(wù)
使用Task.WhenAll
可以并行執(zhí)行多個(gè)異步任務(wù):
public async Task ParallelExecutionDemo()
{
var task1 = DoWorkAsync(1);
var task2 = DoWorkAsync(2);
var task3 = DoWorkAsync(3);
await Task.WhenAll(task1, task2, task3);
Console.WriteLine("所有任務(wù)完成");
}
public async Task DoWorkAsync(int id)
{
await Task.Delay(1000);
Console.WriteLine($"任務(wù) {id} 完成");
}
9.2 帶超時(shí)的異步操作
使用Task.WhenAny
和Task.Delay
可以實(shí)現(xiàn)帶超時(shí)的異步操作:
static async Task Main(string[] args)
{
await FetchDataWithTimeoutAsync("http://www.google.com",new TimeSpan(0, 0, 3));
Console.ReadKey();
}
static async Task<string> FetchDataWithTimeoutAsync(string url, TimeSpan timeout)
{
using (var client = new HttpClient())
{
var dataTask = client.GetStringAsync(url);
var timeoutTask = Task.Delay(timeout);
var completedTask = await Task.WhenAny(dataTask, timeoutTask);
if (completedTask == timeoutTask)
{
throw new TimeoutException("操作超時(shí)");
}
return await dataTask;
}
}
10. 結(jié)論
async
和await
極大地簡化了C#中的異步編程,使得編寫高效、響應(yīng)式的應(yīng)用程序變得更加容易。通過將復(fù)雜的異步操作轉(zhuǎn)換為看似同步的代碼,它們提高了代碼的可讀性和可維護(hù)性。
該文章在 2024/12/9 18:38:49 編輯過