[.NET]From Sync to Async

本文記錄「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:從零開始的軟體開發生活

請大家繼續支持 ^_^