本文記錄「No More Deadlocks – Filip Ekberg」的筆記,
從 block ui ... 使用 Task 到 async ... await
環境:Windows Form, .NET 4.5
1.一開始在 UI 上放一個 Button(btnBlockTest) 及一個 Label (lblResult),
在 Button 的 Click 事件中,寫入以下的Code,
private void btnBlockTest_Click(object sender, EventArgs e)
{
//1.block UI
Thread.Sleep(2000);
lblResult.Text = "Hello World";
}
這樣會Block UI..
2.因為執行比較久,所以我們將要執行的事放入Task之中,如下,
private void btnBlockTest_Click(object sender, EventArgs e)
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
return "Hello World!";
}
);
//這裡等待Task執行結束
lblResult.Text = task.Result;
}
但還是會 Block UI..
3.為了不 Block UI,所以使用 Task.ContinueWith ,如下,
private void btnBlockTest_Click(object sender, EventArgs e)
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
return "Hello World!";
}
);
task.ContinueWith(
(completedTask) =>
{
lblResult.Text = completedTask.Result;
}
);
}
雖然不會Block UI了,但 lblResult.Text 卻不會接收到 Task 的回傳值,因為它不在 UI Thread 中執行。
4.如果 Task 中有錯誤呢?
private void btnBlockTest_Click(object sender, EventArgs e)
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
throw new Exception("Task Error");
return "Hello World!";
}
);
task.ContinueWith(
(completedTask) =>
{
lblResult.Text = completedTask.Result;
}
);
}
Exception 會被吃掉,因為不在 UI Thread 中執行 ...
5.所以我們可以判斷 Task.IsFaulted 就可以將 Exception Log 下來,如下,
private void btnBlockTest_Click(object sender, EventArgs e)
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
throw new Exception("Task Error");
return "Hello World!";
}
);
task.ContinueWith(
(completedTask) =>
{
if (completedTask.IsFaulted)
{
Console.WriteLine(completedTask.Exception.ToString());
}else
{
lblResult.Text = completedTask.Result;
}
}
);
}
處理了 Exception,再來就要處理 lblResult.Text 卻不接收到 Task 的回傳值的問題...
6.因為 Task.ContinueWith 並非在 UI Thread 中執行,所以設定 lblResult.Text 需要在 UI Thread 中執行,如下,
private void btnBlockTest_Click(object sender, EventArgs e)
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
return "Hello World!";
}
);
//way1:if the current execution context is on the UI thread.
//task.ContinueWith(
// (completedTask) =>
// {
// lblResult.Text = completedTask.Result;
// }
// , TaskScheduler.FromCurrentSynchronizationContext());
//way2
task.ContinueWith(
(completedTask) =>
{
this.Invoke((MethodInvoker)delegate
{
lblResult.Text = completedTask.Result;
});
}
);
}
嗯...這樣 lblResult.Text 值就有改變了....
7.接下來我們使用 async ... await,如下,
private async void btnBlockTest_Click(object sender, EventArgs e)
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
return "Hello World!";
}
);
lblResult.Text = await task;
}
功能一樣,但程式碼簡潔了許多呢 ...
8.相同的,如果 Task 中發生了錯誤呢?
private async void btnBlockTest_Click(object sender, EventArgs e)
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
throw new Exception("Task Error");
return "Hello World!";
}
);
lblResult.Text = await task;
}
爆了....
9.將 Task 抽到 RunAsync Method,然後用 try ... catch 去包,如下,
private async void btnBlockTest_Click(object sender, EventArgs e)
{
try
{
RunAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
private async void RunAsync()
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
throw new Exception("Task Error");
return "Hello World!";
}
);
lblResult.Text = await task;
}
還是會發生錯誤 .... 因為 RunAsync 回傳值是 void 而不是 Task。
10.那我們將它改成 Task 呢?
private async Task RunAsync()
{
var task = Task.Run(
() =>
{
Thread.Sleep(2000);
throw new Exception("Task Error");
return "Hello World!";
}
);
lblResult.Text = await task;
}
雖然不會整個 Ap 都掛掉,但也沒有被 catch 攔到... 因為呼叫時,沒有加上 await ...
11.Task的Method要回傳Task,而呼叫則要加上 await,如下,
private async void btnBlockTest_Click(object sender, EventArgs e)
{
try
{
await RunAsync();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
另外使用 Task 上也要小心 相同 Thread Blocking 狀況哦...
因為這樣就會發生 Deadlock ..... 例如 No More Deadlocks – Filip Ekberg 的範例,
Task.Delay(1).ContinueWith((t) => {
}, TaskScheduler.FromCurrentSynchronizationContext()
).Wait();
Demo Project: https://github.com/rainmakerho/Sync2AsyncDemo
參考資料
No More Deadlocks – Filip Ekberg
await與Task.Result/Task.Wait()的Deadlock問題
Hi,
亂馬客Blog已移到了 「亂馬客 : Re:從零開始的軟體開發生活」
請大家繼續支持 ^_^