前言
你是不是也遇到過(guò)這樣的情況:
明明寫(xiě)了個(gè) Task.Run
看起來(lái)沒(méi)問(wèn)題,結(jié)果運(yùn)行的時(shí)候卻出奇奇怪怪的問(wèn)題?
比如循環(huán)變量不對(duì)勁、程序卡死了、異常還悄無(wú)聲息地消失了……這哪是寫(xiě)代碼啊,簡(jiǎn)直像踩地雷。
其實(shí),這些問(wèn)題的背后,往往都藏著幾個(gè)常見(jiàn)的 Task
陷阱。
今天我們就來(lái)聊聊其中最經(jīng)典的“三宗罪”——閉包陷阱、Result 死鎖陷阱、異常被吃陷阱。
準(zhǔn)備好避開(kāi)它們了嗎?Let’s go!
1. 閉包陷阱
這是新手最容易踩的第一個(gè)坑,尤其是在循環(huán)中使用 Task.Run
或 lambda 表達(dá)式
時(shí)。
比如下面這個(gè)例子:
for (int i = 0; i < 5; i++)
{
// 錯(cuò)誤!所有任務(wù)都會(huì)看到i=5
Task.Run(() => Console.WriteLine(i));
}
這段代碼中的 lambda 表達(dá)式捕獲的是變量 i
的引用,而不是值。當(dāng)所有任務(wù)真正開(kāi)始執(zhí)行時(shí),循環(huán)早就結(jié)束了,此時(shí) i
的值已經(jīng)是 5
正確的做法應(yīng)該是:
for (int i = 0; i < 5; i++)
{
int temp = i;
Task.Run(() => Console.WriteLine(temp));
}
記?。?/strong>
在循環(huán)中使用 Task.Run
或 lambda
時(shí),記得把循環(huán)變量賦值給一個(gè)臨時(shí)變量再使用,避免閉包帶來(lái)的副作用
2. Result 死鎖陷阱
這個(gè)陷阱特別喜歡出現(xiàn)在 UI 應(yīng)用(比如 WPF、WinForms)或 ASP.NET 這類(lèi)有同步上下文的環(huán)境中。
比如下面這個(gè)例子:
// 錯(cuò)誤!在UI線(xiàn)程調(diào)用會(huì)死鎖
var result = GetDataAsync().Result;
async Task<string> GetDataAsync()
{
await Task.Delay(1000);
return "Data";
}
為什么會(huì)死鎖?因?yàn)椋?/span>
GetDataAsync()
內(nèi)部用了 await
,它會(huì)在當(dāng)前同步上下文中繼續(xù)執(zhí)行后續(xù)代碼。- 但主線(xiàn)程又在等
.Result
,導(dǎo)致互相等待,直接卡死!
正確的做法應(yīng)該是:
var result = await GetDataAsync();
記?。?/strong>
不要在 UI 或 ASP.NET 等同步上下文中使用 .Result
或 .Wait()
,推薦使用 await
替代。
3. 異常被吃陷阱
你以為在 Task
中拋出了異常就會(huì)看到錯(cuò)誤信息?錯(cuò)!如果不用正確的方式處理,Task
中的異??赡軙?huì)悄無(wú)聲息地消失……
比如下面這個(gè)例子:
// 錯(cuò)誤!異常不會(huì)自動(dòng)拋出,也不會(huì)顯示在控制臺(tái)
// 因?yàn)?Task.Run 啟動(dòng)的任務(wù)是異步執(zhí)行的,
// 如果你不 await 它,也不調(diào)用 .Exception,那異常就像石沉大海一樣,根本沒(méi)人知道發(fā)生了什么!
Task.Run(() => { throw new Exception("Oops!"); });
正確的做法應(yīng)該是:
try
{
await Task.Run(() => { throw new Exception("Oops!"); });
}
catch (Exception ex)
{
Console.WriteLine($"捕獲異常: {ex.Message}");
}
或者這樣:
Task task = Task.Run(() => { throw new Exception("Oops!"); });
task.ContinueWith(t =>
{
if (t.Exception != null)
{
Console.WriteLine($"任務(wù)失?。? + t.Exception.InnerException.Message);
}
});
記住:
只要是異步任務(wù),一定要用 await
或者檢查 Exception
屬性,否則異常會(huì)被“吞掉”
總結(jié)
Task 很強(qiáng),但得小心用,
這些看似不起眼的小細(xì)節(jié),如果不注意,輕則邏輯錯(cuò)誤,重則程序崩潰甚至死鎖,后果不堪設(shè)想。
該文章在 2025/6/18 10:00:43 編輯過(guò)