2019年2月9日 星期六

Wait() 會變同步阻塞一直到Task完成

https://blog.darkthread.net/blog/await-task-block-deadlock/?fbclid=IwAR14EVpNctRsjyoDNTgguv91i8FCKd0piP224xAGK3J_f4UZUr6fvn_iW5Q

using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace asyn2
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
try
{
RunAsync().Wait();
MessageBox.Show("have to wait RunAsync()跑完嗎? ================ ");

}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}

private async Task RunAsync()
{
CancellationTokenSource cts = new CancellationTokenSource(10000);
CancellationToken ct = cts.Token;
string googleSource = await DownloadSourceAsync("https://www.google.com.tw/", ct);
MessageBox.Show(googleSource.Length.ToString());
}
private async Task<string> DownloadSourceAsync(string url, CancellationToken ct)
{
using (HttpClient httpClient = new HttpClient())
{
if (ct.IsCancellationRequested)
{
ct.ThrowIfCancellationRequested();
}
string source = await httpClient.GetStringAsync(url);
return source;
}
}
}
}


用.Wait() 會變同步阻塞一直到Task完成。
用await 會非同步等待,一直到Task完成。


你用了.Wait()造成了問題,你應該一路用async/await走下去才對。
為何用.Wait()會卡死?
因為而你的Task RunAsync() 中又去叫用DownloadSourceAsync 做 httpClient.GetStringAsync()
它根本就完成不了。 你可以在debug mode中,於string source = await httpClient.GetStringAsync(url); 這行放中斷點。
當執行到這中斷點時,你按下F11(快捷鍵:逐行執行當下那一行),你會發現你什麼都不能動了,完全阻塞,UI也都完全無反應。

要解決這問題很簡單,把.Wait()換掉,改用await
把private void button1_Click(object sender, EventArgs e)
改private async void button1_Click(object sender, EventArgs e)
還有
把RunAsync().Wait();
改await RunAsync();
你的程式就能動了,迴避掉了同步阻塞。
並且在MessageBox.Show(googleSource.Length.ToString()); 等待按下"確定"後(即RunAsync()完成後),再到 MessageBox.Show("have to wait RunAsync()跑完嗎? ================ "); 

另一解法是直接用ConfigureAwait(false)
把RunAsync().Wait();
改RunAsync().ConfigureAwait(false);
那麼程式就會一路往前衝,直接跳出兩個MessageBox!它無視await RunAsync();的結果而不等待。
但是我很不建議這樣做,因為ConfigureAwait(False)不應該被加在UI線程中(你的button1_Click就是UI線程)。
而且不建議以同步的方式執行非同步的呼叫,建議在UI線程中應該一路用async/await走下去。

沒有留言:

張貼留言