C#多線程TPL模式高級(jí)用法探秘
一、引言
我們先來(lái)看下面的一個(gè)小示例:一個(gè)Winfrom程序,界面上有一個(gè)按鈕,有兩個(gè)異步方法,點(diǎn)擊按鈕調(diào)用兩個(gè)異步方法,彈出執(zhí)行順序,代碼如下:
using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace TPLDemoSln { public partial class Form1 : Form { public Form1() { InitializeComponent(); } /// <summary> /// 按鈕點(diǎn)擊事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnStart_Click(object sender, EventArgs e) { string i1 = await F1Async(); MessageBox.Show("i1=" + i1); string i2 = await F2Async(); MessageBox.Show("i2=" + i2); } /// <summary> /// 異步方法F1 /// </summary> /// <returns></returns> private Task<string> F1Async() { MessageBox.Show("F1 Start"); return Task.Run<string>(() => { // 休眠1秒 Thread.Sleep(1000); MessageBox.Show("F1 Run"); return "F1"; }); } /// <summary> /// 異步方法F2 /// </summary> /// <returns></returns> private Task<string> F2Async() { MessageBox.Show("F2 Start"); return Task.Run<string>(() => { // 休眠2秒 Thread.Sleep(2000); MessageBox.Show("F2 Run"); return "F2"; }); } } }
在上面的代碼中,Task.Run()是用來(lái)把一個(gè)代碼段包裝為T(mén)ask<T>的方法,Run中委托的代碼體就是異步任務(wù)執(zhí)行的邏輯,最后return返回值。
運(yùn)行程序,可以得到如下的輸出順序:
F1 Start->F1 Run->i1=F1->F2 Start->F2 Run->i2=F2。
我們對(duì)按鈕事件進(jìn)行修改,修改為下面的代碼,在看看執(zhí)行順序:
/// <summary> /// 按鈕點(diǎn)擊事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnStart_Click(object sender, EventArgs e) { //string i1 = await F1Async(); //MessageBox.Show("i1=" + i1); //string i2 = await F2Async(); //MessageBox.Show("i2=" + i2); Task<string> task1 = F1Async(); Task<string> task2 = F2Async(); string i1 = await task1; MessageBox.Show("i1=" + i1); string i2 = await task2; MessageBox.Show("i2=" + i2); }
再次運(yùn)行程序,查看輸出順序:
F1 Start->F2 Start->F1 Run->i1=F1->F2 Run->i2=F2。
可以看出兩次的執(zhí)行順序不一致。這是什么原因呢?
這是因?yàn)椴⒉皇堑搅薬wait才開(kāi)始執(zhí)行Task異步任務(wù),執(zhí)行到Task<string> task1=F1Async()這句代碼的時(shí)候,F(xiàn)1Async異步任務(wù)就開(kāi)始執(zhí)行了。同理,執(zhí)行到下一句代碼就開(kāi)始執(zhí)行F2Async異步任務(wù)了。await是為了保證執(zhí)行到這里的時(shí)候異步任務(wù)一定執(zhí)行完。執(zhí)行到await的時(shí)候,如果異步任務(wù)還沒(méi)有執(zhí)行,那么就等待異步任務(wù)執(zhí)行完。如果異步任務(wù)已經(jīng)執(zhí)行完了,那么就直接獲取異步任務(wù)的返回值。
我們上面解釋的原因是否正確呢?我們可以做下面的一個(gè)實(shí)驗(yàn),來(lái)驗(yàn)證上面說(shuō)的原因,我們把按鈕事件里面的await注釋掉,看看異步方法還會(huì)不會(huì)執(zhí)行,代碼如下:
/// <summary> /// 按鈕點(diǎn)擊事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private async void btnStart_Click(object sender, EventArgs e) { //string i1 = await F1Async(); //MessageBox.Show("i1=" + i1); //string i2 = await F2Async(); //MessageBox.Show("i2=" + i2); Task<string> task1 = F1Async(); Task<string> task2 = F2Async(); // 并不是到了await才開(kāi)始執(zhí)行Task異步任務(wù),這里是保證異步任務(wù)一定執(zhí)行完 // 代碼執(zhí)行到這里的時(shí)候,如果沒(méi)有執(zhí)行完就等待執(zhí)行完,如果已經(jīng)執(zhí)行完,就直接獲取返回值 // 這里注釋掉await代碼段,查看異步方法是否會(huì)執(zhí)行 //string i1 = await task1; //MessageBox.Show("i1=" + i1); //string i2 = await task2; //MessageBox.Show("i2=" + i2); }
運(yùn)行程序,發(fā)現(xiàn)異步方法還是會(huì)執(zhí)行,這就說(shuō)明我們上面解釋的原因是正確的。感興趣的可以使用Reflector反編譯查看內(nèi)部實(shí)現(xiàn)的原理,主要是MoveNext()方法內(nèi)部。
我們可以得到下面的結(jié)論:
- 只要方法是Task<T>類(lèi)型的返回值,都可以用await來(lái)等待調(diào)用獲取返回值。
- 如果一個(gè)返回Task<T>類(lèi)型的方法被標(biāo)記了async,那么只要方法內(nèi)部直接return T這個(gè)類(lèi)型的實(shí)例就可以了。
- 一個(gè)返回Task<T>類(lèi)型的方法如果沒(méi)有被標(biāo)記為async,那么需要方法內(nèi)部直接return一個(gè)Task的實(shí)例。
上面說(shuō)的第二點(diǎn)看下面的代碼
/// <summary> /// 方法標(biāo)記為async 直接返回一個(gè)int類(lèi)型的數(shù)值即可 /// </summary> /// <returns></returns> private async Task<int> F3Async() { return 2; }
上面的第三點(diǎn)可以看下面的代碼:
/// <summary> /// 方法沒(méi)有被標(biāo)記為async,直接返回一個(gè)Task /// </summary> /// <returns></returns> private Task<int> F4Async() { return Task.Run<int>(() => { return 2; }); }
二、TPL高級(jí)
我們做一些總結(jié):
1、如果方法內(nèi)部有await,則方法必須標(biāo)記為async。await和async是成對(duì)出現(xiàn)的,只有await沒(méi)有async程序會(huì)報(bào)錯(cuò)。只有async沒(méi)有await,程序會(huì)按照同步方法執(zhí)行。
2、ASP.NET MVC中的Action方法和WinForm中的事件處理方法都可以標(biāo)記為async,控制臺(tái)的Main()方法不能被標(biāo)記為async。對(duì)于不能標(biāo)記為async的方法怎么辦呢?我們可以使用Result屬性來(lái)獲取值,看下面代碼:
using System; using System.Net.Http; using System.Threading.Tasks; namespace AsyncDemo { class Program { static void Main(string[] args) { // 實(shí)例化對(duì)象 HttpClient client = new HttpClient(); // 調(diào)用異步的Get方法 Task<HttpResponseMessage> taskMsg = client.GetAsync("http://www.baidu.com"); // 通過(guò)Result屬性獲取返回值 HttpResponseMessage msg = taskMsg.Result; Task<string> taskRead = msg.Content.ReadAsStringAsync(); string html = taskRead.Result; Console.WriteLine(html); Console.ReadKey(); } } }
不建議使用這種方式,這樣體現(xiàn)不出異步帶來(lái)的好處,而且使用Result屬性,有可能會(huì)帶來(lái)上下文切換造成的死鎖。
下面我們來(lái)看看創(chuàng)建Task的方法。
1、如果返回值就是一個(gè)立即可以隨手得到的值,那么就用Task.FromResult()??聪旅娲a:
static Task<int> TestAsync() { //return Task.Run<int>(() => //{ // return 5; //}); // 簡(jiǎn)便寫(xiě)法 return Task.FromResult(3); }
2、如果是一個(gè)需要休息一會(huì)的任務(wù)(比如下載失敗則過(guò)5秒鐘后重試。主線程不休息,和Thread.Sleep不一樣),那么就用Task.Delay()。
3、Task.Factory.FromAsync()會(huì)把IAsyncResult轉(zhuǎn)換為T(mén)ask,這樣APM風(fēng)格的API也可以用await來(lái)調(diào)用。
4、編寫(xiě)異步方法的簡(jiǎn)化寫(xiě)法。如果方法聲明為async,那么可以直接return具體的值,不用在創(chuàng)建Task,由編譯器創(chuàng)建Task,看下面的代碼:
static async Task<int> Test2Async() { // 復(fù)雜寫(xiě)法 //return await Task.Run<int>(() => //{ // return 5; //}); // 下面是簡(jiǎn)化寫(xiě)法,直接返回 return 6; }
到此這篇關(guān)于C#多線程TPL模式高級(jí)用法探秘的文章就介紹到這了。希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C# 控件屬性和InitializeComponent()關(guān)系案例詳解
這篇文章主要介紹了C# 控件屬性和InitializeComponent()關(guān)系案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08C#中判斷某類(lèi)型是否可以進(jìn)行隱式類(lèi)型轉(zhuǎn)換
在我們采用反射動(dòng)態(tài)調(diào)用一些方法時(shí),常常涉及到類(lèi)型的轉(zhuǎn)換,直接判斷類(lèi)型是否相符有時(shí)不能判斷調(diào)用方法是否合適2013-04-04C#中DataGridView導(dǎo)出Excel的兩種方法
這篇文章主要介紹了C#中DataGridView導(dǎo)出Excel的兩種方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01C#實(shí)現(xiàn)鼠標(biāo)左右鍵切換效果
這篇文章主要為大家詳細(xì)介紹了如何利用C#實(shí)現(xiàn)鼠標(biāo)左右鍵切換功能,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)C#有一定的幫助,感興趣的小伙伴可以跟隨小編一起了解一下2022-12-12C#基礎(chǔ)知識(shí)之GetType與typeof的區(qū)別小結(jié)
在比較對(duì)象時(shí),需要了解他們的類(lèi)型,才能決定他們的值是否能比較。所有的類(lèi)都從System.Object中繼承了GetType()方法,常常與typeo()運(yùn)算符一起使用。這篇文章主要給大家介紹了關(guān)于C#基礎(chǔ)知識(shí)之GetType與typeof區(qū)別的相關(guān)資料,需要的朋友可以參考下2021-06-06