C#多線程TPL模式高級用法探秘
一、引言
我們先來看下面的一個小示例:一個Winfrom程序,界面上有一個按鈕,有兩個異步方法,點(diǎn)擊按鈕調(diào)用兩個異步方法,彈出執(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()是用來把一個代碼段包裝為Task<T>的方法,Run中委托的代碼體就是異步任務(wù)執(zhí)行的邏輯,最后return返回值。
運(yùn)行程序,可以得到如下的輸出順序:
F1 Start->F1 Run->i1=F1->F2 Start->F2 Run->i2=F2。
我們對按鈕事件進(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í)行順序不一致。這是什么原因呢?
這是因為并不是到了await才開始執(zhí)行Task異步任務(wù),執(zhí)行到Task<string> task1=F1Async()這句代碼的時候,F(xiàn)1Async異步任務(wù)就開始執(zhí)行了。同理,執(zhí)行到下一句代碼就開始執(zhí)行F2Async異步任務(wù)了。await是為了保證執(zhí)行到這里的時候異步任務(wù)一定執(zhí)行完。執(zhí)行到await的時候,如果異步任務(wù)還沒有執(zhí)行,那么就等待異步任務(wù)執(zhí)行完。如果異步任務(wù)已經(jīng)執(zhí)行完了,那么就直接獲取異步任務(wù)的返回值。
我們上面解釋的原因是否正確呢?我們可以做下面的一個實(shí)驗,來驗證上面說的原因,我們把按鈕事件里面的await注釋掉,看看異步方法還會不會執(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才開始執(zhí)行Task異步任務(wù),這里是保證異步任務(wù)一定執(zhí)行完
// 代碼執(zhí)行到這里的時候,如果沒有執(zhí)行完就等待執(zhí)行完,如果已經(jīng)執(zhí)行完,就直接獲取返回值
// 這里注釋掉await代碼段,查看異步方法是否會執(zhí)行
//string i1 = await task1;
//MessageBox.Show("i1=" + i1);
//string i2 = await task2;
//MessageBox.Show("i2=" + i2);
}運(yùn)行程序,發(fā)現(xiàn)異步方法還是會執(zhí)行,這就說明我們上面解釋的原因是正確的。感興趣的可以使用Reflector反編譯查看內(nèi)部實(shí)現(xiàn)的原理,主要是MoveNext()方法內(nèi)部。
我們可以得到下面的結(jié)論:
- 只要方法是Task<T>類型的返回值,都可以用await來等待調(diào)用獲取返回值。
- 如果一個返回Task<T>類型的方法被標(biāo)記了async,那么只要方法內(nèi)部直接return T這個類型的實(shí)例就可以了。
- 一個返回Task<T>類型的方法如果沒有被標(biāo)記為async,那么需要方法內(nèi)部直接return一個Task的實(shí)例。
上面說的第二點(diǎn)看下面的代碼
/// <summary>
/// 方法標(biāo)記為async 直接返回一個int類型的數(shù)值即可
/// </summary>
/// <returns></returns>
private async Task<int> F3Async()
{
return 2;
}上面的第三點(diǎn)可以看下面的代碼:
/// <summary>
/// 方法沒有被標(biāo)記為async,直接返回一個Task
/// </summary>
/// <returns></returns>
private Task<int> F4Async()
{
return Task.Run<int>(() =>
{
return 2;
});
}二、TPL高級
我們做一些總結(jié):
1、如果方法內(nèi)部有await,則方法必須標(biāo)記為async。await和async是成對出現(xiàn)的,只有await沒有async程序會報錯。只有async沒有await,程序會按照同步方法執(zhí)行。
2、ASP.NET MVC中的Action方法和WinForm中的事件處理方法都可以標(biāo)記為async,控制臺的Main()方法不能被標(biāo)記為async。對于不能標(biāo)記為async的方法怎么辦呢?我們可以使用Result屬性來獲取值,看下面代碼:
using System;
using System.Net.Http;
using System.Threading.Tasks;
namespace AsyncDemo
{
class Program
{
static void Main(string[] args)
{
// 實(shí)例化對象
HttpClient client = new HttpClient();
// 調(diào)用異步的Get方法
Task<HttpResponseMessage> taskMsg = client.GetAsync("http://www.baidu.com");
// 通過Result屬性獲取返回值
HttpResponseMessage msg = taskMsg.Result;
Task<string> taskRead = msg.Content.ReadAsStringAsync();
string html = taskRead.Result;
Console.WriteLine(html);
Console.ReadKey();
}
}
}不建議使用這種方式,這樣體現(xiàn)不出異步帶來的好處,而且使用Result屬性,有可能會帶來上下文切換造成的死鎖。
下面我們來看看創(chuàng)建Task的方法。
1、如果返回值就是一個立即可以隨手得到的值,那么就用Task.FromResult()??聪旅娲a:
static Task<int> TestAsync()
{
//return Task.Run<int>(() =>
//{
// return 5;
//});
// 簡便寫法
return Task.FromResult(3);
}2、如果是一個需要休息一會的任務(wù)(比如下載失敗則過5秒鐘后重試。主線程不休息,和Thread.Sleep不一樣),那么就用Task.Delay()。
3、Task.Factory.FromAsync()會把IAsyncResult轉(zhuǎn)換為Task,這樣APM風(fēng)格的API也可以用await來調(diào)用。
4、編寫異步方法的簡化寫法。如果方法聲明為async,那么可以直接return具體的值,不用在創(chuàng)建Task,由編譯器創(chuàng)建Task,看下面的代碼:
static async Task<int> Test2Async()
{
// 復(fù)雜寫法
//return await Task.Run<int>(() =>
//{
// return 5;
//});
// 下面是簡化寫法,直接返回
return 6;
}到此這篇關(guān)于C#多線程TPL模式高級用法探秘的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
C#實(shí)現(xiàn)拷貝文件的9種方法小結(jié)
最近遇一個問題,一個程序調(diào)用另一個程序的文件,結(jié)果另一個程序的文件被占用,使用不了文件,這時候的解決方案就是把另一個程序的文件拷貝到當(dāng)前程序就可以了,本文介紹用C#拷貝文件的多種方式,需要的朋友可以參考下2024-04-04
C#實(shí)現(xiàn)計算器功能(winform版)
這篇文章主要為大家詳細(xì)介紹了C#實(shí)現(xiàn)winform版的計算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
Unity?UGUI的MaskableGraphic可遮罩圖形組件介紹使用
這篇文章主要為大家介紹了Unity?UGUI的MaskableGraphic可遮罩圖形組件介紹使用,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07
C# 向Word中設(shè)置/更改文本方向的方法(兩種)
在一般情況下word中輸入的文字都是橫向的,今天小編給大家?guī)韮煞N方法來設(shè)置更改文本方向的方法,非常不錯,對c# word 更改文本方向的知識感興趣的朋友一起看看吧2016-08-08
Graphics.DrawImage繪制的圖像變大的原因分析及解決
這篇文章主要介紹了Graphics.DrawImage繪制的圖像變大的原因分析及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11
使用c#實(shí)現(xiàn)隨機(jī)數(shù)猜數(shù)游戲的示例代碼
這篇文章主要介紹了使用c#實(shí)現(xiàn)隨機(jī)數(shù)猜數(shù)游戲的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09

