其實早在.NET 4.5的時候M$就在.NET中引入了async和await關鍵字(VB爲Async和Await)來簡化異步調用的編程模式。我也早就體驗過了,如今寫一篇日誌來記錄一下順便湊日誌數量(之後面試以前能夠用這個「複習」一下)。面試
(一)傳統的異步調用編程
在比較「古老」的C#程序中常常能夠看到IAsyncResult、BeginInvoke之類的異步調用「蹤影」。先來簡單的複習一下吧。app
假如咱們有一個方法生成字符串,而生成這個字符串須要10秒中的時間:異步
for (int i = begin; i < begin + length; i++)
{
sb.Append(WasteTime(i) + " ");
}async
return sb.ToString();
}ui
private string WasteTime(int current)
{
System.Threading.Thread.Sleep(1000);
return current.ToString();
}
}this
咱們再作一個窗口,用來請求這個方法並把字符串顯示到文本框中。使用同步調用確定會把UI線程阻塞掉,要想不把UI阻塞掉就要另起一個線程了。基本的步驟以下:url
建立一個異步調用的委託:spa
而後呢,再異步調用這個委託:線程
textBox1.Text = "Requesting string, please wait...";
IAsyncResult ar = d.BeginInvoke(1, 10, TaskComplete, d);
}
這裏的BeginInvoke會在原來的基礎上再附加兩個參數:表示執行完畢後的回調方法AsyncCallBack,最後一個參數能夠是任何對象,以便從回調方法中訪問它。不過通常狀況都是傳遞的委託實例,以便獲取調用的結果。
固然咱們也能夠不用回調方法,這樣就只好不斷地循環查詢是否執行完成了。
而後咱們就要編寫AsyncCallBack這個回調方法了,它接受一個IAsyncResult類型的對象表示異步調用的結果:
調用委託實例的EndInvoke方法並傳入IAsyncResult類型的對象用以獲取GetSlowString的返回結果。
回調方法是委託線程調用的,所以它不能直接訪問UI,因此咱們使用窗體的Invoke方法在主線程中顯示結果。若是委託方法拋出異常,將會在EndInvoke時拋出。
(二)使用Task類型
能夠看到使用傳統的辦法編寫異步調用很麻煩,特別是若是這種調用不少,那麼咱們的程序就會變成很複雜,邏輯很亂。
.NET 4.5提供的新的異步變成模式就很好地解決了這個問題(其實本質上應該是.NET自動實現了不少操做),使編寫異步代碼和同步調用同樣邏輯清晰。
首先來看看微軟的例子:
// Equivalently, now that you see how it works, you can write the same thing in a single line.
byte[] urlContents = await client.GetByteArrayAsync(url);
// . . .
}
能夠看出,使用await關鍵字後,.NET會自動把返回結果包裝在一個Task類型的對象中。對於這個示例,方法是沒有返回結果的。而對有返回結果的方法,就要使用Task<T>了:
總而言之,使用await表達式時,控制會返回到調用此方法的線程中;在await等待的方法執行完畢後,控制會自動返回到下面的語句中。發生異常時,異常會在await表達式中拋出。
對於咱們這個例子,咱們編寫的代碼以下:
WasteTimeObject ad = new WasteTimeObject();
string result = await Task.Run(() => ad.GetSlowString(1, 10));
//Update UI to display the result
textBox1.Text = result;
}
咱們使用Task類新建一個工做線程並執行。固然咱們也能夠像M$給的例子那樣改造一下GetSlowString,這樣就不須要加上Task.Run了。(基本上,這種方法都會以Async後綴結尾。)
如何?原來的:建立異步委託→回調一鼓作氣。另外還有一點,await下面的語句是由主線程調用的,不是由新的線程調用,因此咱們能夠直接訪問UI。
(三)取消執行和顯示進度
最後一個要記錄的,就是如何給異步調用添加進度條,並能讓用戶取消操做。界面就是下面這樣:
使用最終完成的代碼來講明吧。首先改造GetSlowString方法,使之支持取消和彙報進度:
for (int i = begin; i < begin + length; i++)
{
sb.Append(WasteTime(i) + " ");
cancel.ThrowIfCancellationRequested();
if (progress != null)
progress.Report((int)((double)(i - begin + 1) * 100 / length));
}
return sb.ToString();
}
IProgress<T>類型的對象有一個Report方法,執行這個方法實際上會調用自定義的更新進度的方法,這個方法(使用委託或匿名方法皆可)是在生成Progress<T>對象的時候指定的:
神奇的是,這個方法是由主線程調用的,若是不是這樣,它就不能更新咱們界面上的控件。因此說微軟提供的新機制幫咱們簡化了不少工做。
CancellationToken用於指定該方法「綁定」的取消上下文,若是這個對象執行過Cancel方法(用戶點擊了Cancel按鈕),那麼訪問ThrowIfCancellationRequested時就會拋出OperationCanceledException類型的異常。這種機制的靈活性在於停止執行的位置是能夠自行肯定的,不會出現取消時本身都不知道執行到哪行代碼的狀況。
總而言之,單擊request按鈕的代碼咱們修改以下:
cancelSource = new CancellationTokenSource();
IProgress<int> progress = new Progress<int>((progressValue) => { progressBar1.Value = progressValue; });
textBox1.Text = "Requesting string, please wait...";
button1.Enabled = false; button2.Enabled = true;
WasteTimeObject ad = new WasteTimeObject();
try
{
string result = await Task.Run(() => ad.GetSlowString(1, 10, progress, cancelSource.Token),
cancelSource.Token);
//Update UI to display the result
textBox1.Text = result;
button2.Enabled = false; //Disable cancel button
}
catch (OperationCanceledException)
{
textBox1.Text = "You canceled the operation.";
}
}
取消按鈕的代碼就很簡單了:
至此,Task機制的初步體驗就到此完成。之後有機會在研究下更高階的內容吧。