.NET中的async和await關(guān)鍵字使用及Task異步調(diào)用實(shí)例
其實(shí)早在.NET 4.5的時(shí)候M$就在.NET中引入了async和await關(guān)鍵字(VB為Async和Await)來簡(jiǎn)化異步調(diào)用的編程模式。我也早就體驗(yàn)過了,現(xiàn)在寫一篇日志來記錄一下順便湊日志數(shù)量(以后面試之前可以用這個(gè)“復(fù)習(xí)”一下)。
(一)傳統(tǒng)的異步調(diào)用
在比較“古老”的C#程序中經(jīng)??梢钥吹絀AsyncResult、BeginInvoke之類的異步調(diào)用“蹤跡”。先來簡(jiǎn)單的復(fù)習(xí)一下吧。
假如我們有一個(gè)方法生成字符串,而生成這個(gè)字符串需要10秒中的時(shí)間:
public class WasteTimeObject
{
public string GetSlowString(int begin, int length)
{
StringBuilder sb = new StringBuilder();
for (int i = begin; i < begin + length; i++)
{
sb.Append(WasteTime(i) + " ");
}
return sb.ToString();
}
private string WasteTime(int current)
{
System.Threading.Thread.Sleep(1000);
return current.ToString();
}
}
我們?cè)僮鲆粋€(gè)窗口,用來請(qǐng)求這個(gè)方法并把字符串顯示到文本框中。使用同步調(diào)用肯定會(huì)把UI線程阻塞掉,要想不把UI阻塞掉就要另起一個(gè)線程了?;镜牟襟E如下:
創(chuàng)建一個(gè)異步調(diào)用的委托:
public delegate string GetSlowStringDelegate(int begin, int length);
然后呢,再異步調(diào)用這個(gè)委托:
private void button1_Click(object sender, EventArgs e)
{
WasteTimeObject ad = new WasteTimeObject();
GetSlowStringDelegate d = ad.GetSlowString;
textBox1.Text = "Requesting string, please wait...";
IAsyncResult ar = d.BeginInvoke(1, 10, TaskComplete, d);
}
這里的BeginInvoke會(huì)在原來的基礎(chǔ)上再附加兩個(gè)參數(shù):表示執(zhí)行完畢后的回調(diào)方法AsyncCallBack,最后一個(gè)參數(shù)可以是任何對(duì)象,以便從回調(diào)方法中訪問它。不過一般情況都是傳遞的委托實(shí)例,以便獲取調(diào)用的結(jié)果。
當(dāng)然我們也可以不用回調(diào)方法,這樣就只好不斷地循環(huán)查詢是否執(zhí)行完成了。
然后我們就要編寫AsyncCallBack這個(gè)回調(diào)方法了,它接受一個(gè)IAsyncResult類型的對(duì)象表示異步調(diào)用的結(jié)果:
private void TaskComplete(IAsyncResult ar)
{
if (ar == null) return;
GetSlowStringDelegate d = ar.AsyncState as GetSlowStringDelegate;
if (d == null) throw new Exception("Invalue object type");
string result = d.EndInvoke(ar);
this.Invoke(new Action(() => UpdateTextResult(result)));
}
調(diào)用委托實(shí)例的EndInvoke方法并傳入IAsyncResult類型的對(duì)象用以獲取GetSlowString的返回結(jié)果。
回調(diào)方法是委托線程調(diào)用的,因此它不能直接訪問UI,所以我們使用窗體的Invoke方法在主線程中顯示結(jié)果。如果委托方法拋出異常,將會(huì)在EndInvoke時(shí)拋出。
(二)使用Task類型
可以看到使用傳統(tǒng)的辦法編寫異步調(diào)用很麻煩,特別是如果這種調(diào)用很多,那么我們的程序就會(huì)變成很復(fù)雜,邏輯很亂。
.NET 4.5提供的新的異步變成模式就很好地解決了這個(gè)問題(其實(shí)本質(zhì)上應(yīng)該是.NET自動(dòng)實(shí)現(xiàn)了很多操作),使編寫異步代碼和同步調(diào)用一樣邏輯清晰。
首先來看看微軟的例子:
private async Task SumPageSizesAsync()
{
// To use the HttpClient type in desktop apps, you must include a using directive and add a
// reference for the System.Net.Http namespace.
HttpClient client = new HttpClient();
// 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關(guān)鍵字后,.NET會(huì)自動(dòng)把返回結(jié)果包裝在一個(gè)Task類型的對(duì)象中。對(duì)于這個(gè)示例,方法是沒有返回結(jié)果的。而對(duì)有返回結(jié)果的方法,就要使用Task<T>了:
public async Task<string> WaitAsynchronouslyAsync()
{
await Task.Delay(10000);
return "Finished";
}
總而言之,使用await表達(dá)式時(shí),控制會(huì)返回到調(diào)用此方法的線程中;在await等待的方法執(zhí)行完畢后,控制會(huì)自動(dòng)返回到下面的語(yǔ)句中。發(fā)生異常時(shí),異常會(huì)在await表達(dá)式中拋出。
對(duì)于我們這個(gè)例子,我們編寫的代碼如下:
private async void button1_Click(object sender, EventArgs e)
{
textBox1.Text = "Requesting string, please wait...";
WasteTimeObject ad = new WasteTimeObject();
string result = await Task.Run(() => ad.GetSlowString(1, 10));
//Update UI to display the result
textBox1.Text = result;
}
我們使用Task類新建一個(gè)工作線程并執(zhí)行。當(dāng)然我們也可以像M$給的例子那樣改造一下GetSlowString,這樣就不需要加上Task.Run了。(基本上,這種方法都會(huì)以Async后綴結(jié)尾。)
如何?原來的:創(chuàng)建異步委托→回調(diào)一氣呵成。另外還有一點(diǎn),await下面的語(yǔ)句是由主線程調(diào)用的,不是由新的線程調(diào)用,所以我們可以直接訪問UI。
(三)取消執(zhí)行和顯示進(jìn)度
最后一個(gè)要記錄的,就是如何給異步調(diào)用添加進(jìn)度條,并能讓用戶取消操作。界面就是下面這樣:
使用最終完成的代碼來說明吧。首先改造GetSlowString方法,使之支持取消和匯報(bào)進(jìn)度:
public string GetSlowString(int begin, int length, IProgress<int> progress, CancellationToken cancel)
{
StringBuilder sb = new StringBuilder();
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>類型的對(duì)象有一個(gè)Report方法,執(zhí)行這個(gè)方法實(shí)際上會(huì)調(diào)用自定義的更新進(jìn)度的方法,這個(gè)方法(使用委托或匿名方法皆可)是在生成Progress<T>對(duì)象的時(shí)候指定的:
IProgress<int> progress = new Progress<int>((progressValue) => { progressBar1.Value = progressValue; });
神奇的是,這個(gè)方法是由主線程調(diào)用的,如果不是這樣,它就不能更新我們界面上的控件。所以說微軟提供的新機(jī)制幫我們簡(jiǎn)化了很多工作。
CancellationToken用于指定該方法“綁定”的取消上下文,如果這個(gè)對(duì)象執(zhí)行過Cancel方法(用戶點(diǎn)擊了Cancel按鈕),那么訪問ThrowIfCancellationRequested時(shí)就會(huì)拋出OperationCanceledException類型的異常。這種機(jī)制的靈活性在于中止執(zhí)行的位置是可以自行確定的,不會(huì)出現(xiàn)取消時(shí)自己都不知道執(zhí)行到哪行代碼的情況。
總而言之,單擊request按鈕的代碼我們修改如下:
private async void button1_Click(object sender, EventArgs e)
{
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.";
}
}
取消按鈕的代碼就很簡(jiǎn)單了:
private void button2_Click(object sender, EventArgs e)
{
if (cancelSource != null) cancelSource.Cancel();
button2.Enabled = false;
}
至此,Task機(jī)制的初步體驗(yàn)就到此完成。以后有機(jī)會(huì)在研究下更高階的內(nèi)容吧。
相關(guān)文章
C#使用LINQ中Enumerable類方法的延遲與立即執(zhí)行的控制
這篇文章主要介紹了C#的LINQ查詢中Enumerable類方法的延遲與立即執(zhí)行,LINQ語(yǔ)言集成查詢可以讓C#和VB以查詢數(shù)據(jù)庫(kù)相同的方式操作內(nèi)存數(shù)據(jù),需要的朋友可以參考下2016-03-03c# 數(shù)據(jù)庫(kù)的 sql 參數(shù)封裝類的編寫
c# 數(shù)據(jù)庫(kù)的 sql 參數(shù)封裝類的編寫...2007-12-12C#使用密封類實(shí)現(xiàn)密封用戶信息的示例詳解
在C#中,密封類(sealed class)是一種不能被其他類繼承的類,它用于防止其他類繼承它的功能和屬性, 下面我們就來看看如何使用密封類密封用戶的信息吧2024-02-02C#函數(shù)式編程中的標(biāo)準(zhǔn)高階函數(shù)詳解
這篇文章主要介紹了C#函數(shù)式編程中的標(biāo)準(zhǔn)高階函數(shù)詳解,本文講解了何為高階函數(shù)、Map、 Filter、Fold等內(nèi)容,需要的朋友可以參考下2015-01-01C#基于WebSocket實(shí)現(xiàn)聊天室功能
這篇文章主要為大家詳細(xì)介紹了C#基于WebSocket實(shí)現(xiàn)聊天室功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02C# 設(shè)置防火墻的創(chuàng)建規(guī)則
這篇文章主要介紹了C# 設(shè)置防火墻的創(chuàng)建規(guī)則,幫助大家更好的利用c#操作防火墻,感興趣的朋友可以了解下2020-11-11DevExpress之SplashScreen用法實(shí)例
這篇文章主要介紹了DevExpress中SplashScreen的用法,對(duì)于C#初學(xué)者有很好的參考借鑒價(jià)值,需要的朋友可以參考下2014-08-08