在.NET Core中使用異步編程的方法步驟
近期對于異步和多線程編程有些啟發(fā),所以我決定把自己的理解寫下來。
思考:為什么要使用異步編程?
我們先看看同步方法和異步方法之前在程序中執(zhí)行的邏輯:
1. 同步方法
static void Main(string[] args)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:開始");
// 調用同步方法
SyncTestMethod();
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:結束");
Console.ReadKey();
}
/// <summary>
/// 同步方法
/// </summary>
static void SyncTestMethod()
{
for (int i = 0; i < 10; i++)
{
var str = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:SyncTestMethod{i}";
Console.WriteLine(str);
Thread.Sleep(10);
}
}
控制臺打印:
2019-03-26 14:44:05 445:開始
2019-03-26 14:44:05 445:SyncTestMethod0
2019-03-26 14:44:05 445:SyncTestMethod1
2019-03-26 14:44:05 445:SyncTestMethod2
2019-03-26 14:44:05 445:SyncTestMethod3
2019-03-26 14:44:05 445:SyncTestMethod4
2019-03-26 14:44:05 445:SyncTestMethod5
2019-03-26 14:44:05 445:SyncTestMethod6
2019-03-26 14:44:05 445:SyncTestMethod7
2019-03-26 14:44:05 445:SyncTestMethod8
2019-03-26 14:44:05 445:SyncTestMethod9
2019-03-26 14:44:05 445:結束
主線程在調用同步方法時,會直接在主線程中執(zhí)行同步方法,這個時候若SyncTestMethod方法后面還有其它方法,都需要等待SyncTestMethod執(zhí)行完成。
2. 異步方法
static void Main(string[] args)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:開始");
// 調用異步步方法
AsyncTestMethod();
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:結束");
Console.ReadKey();
}
/// <summary>
/// 異步方法
/// </summary>
/// <returns></returns>
static async Task AsyncTestMethod() {
await Task.Run(() => {
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"AsyncTestMethod");
Thread.Sleep(10);
}
});
}
控制臺打印:
2019-03-26 14:52:37 5237:開始
2019-03-26 14:52:37 5237:結束
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
2019-03-26 14:52:37 5237:AsyncTestMethod
主線程在調用異步方法時,將會新建一個子線程去執(zhí)行異步方法,調用過AsyncTestMethod方法之后,將會直接執(zhí)行AsyncTestMethod后面的方法,這個時候主線程不會等待異步方法執(zhí)行完成;因為這個時候主線程無法知曉異步方法會在什么時候執(zhí)行完成,所以此時也無法在主線程中直接獲取異步方法的返回,如果需要在異步方法執(zhí)行完成之后再在主線程中執(zhí)行其它方法,則需要使用Wait()來等待異步子線程執(zhí)行完成。

3. 等待(awiat)異步方法
static void Main(string[] args)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:開始");
// 調用異步步方法
AsyncTestMethod();
// 等待異步方法執(zhí)行完成
m1.Wait();
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:結束");
Console.ReadKey();
}
/// <summary>
/// 異步方法
/// </summary>
/// <returns></returns>
static async Task AsyncTestMethod() {
await Task.Run(() => {
for (int i = 0; i < 10; i++)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:AsyncTestMethod");
Thread.Sleep(10);
}
});
}
控制臺打?。?/p>
2019-03-26 14:55:51 5551:開始
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:AsyncTestMethod
2019-03-26 14:55:51 5551:結束
主線程在調用異步方法時,將會新建一個子線程去執(zhí)行異步方法,并且在調用AsyncTestMethod方法之后執(zhí)行了對AsyncTestMethod方法的等待Wait(),這個時候主線程會等待異步方法執(zhí)行完成,不會執(zhí)行后續(xù)的方法,在AsyncTestMethod執(zhí)行完成之后,等待結束,此時可以拿到異步方法AsyncTestMethod的返回值,然后再繼續(xù)執(zhí)行主線程中的方法。

4. 同步線程和異步線程關聯(lián)執(zhí)行
如有以下方法:
static int Method1()
{
Thread.Sleep(200);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:我計算了一個值耗費200ms");
return 1;
}
static int Method200ms()
{
Thread.Sleep(200);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:我做了一件耗費200ms的事情");
return 200;
}
static int Method500ms(int index)
{
Thread.Sleep(500);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:我做了一件耗費500ms的事情");
return ++index;
}
static int Method1000ms()
{
Thread.Sleep(1000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:我做了一件耗費1000ms的事情");
return 1000;
}
Method500ms()需要Method1()的返回值作為參數(shù),如果所有的方法同步執(zhí)行在最后計算a、b、c、d的和:
static void Main(string[] args)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:開始");
var a = Method1();
var b = Method200ms();
var c = Method500ms(a);
var d = Method1000ms();
var result = a+b+c+d;
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:最后得到的結果{result}");
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:結束");
Console.ReadKey();
}
控制臺打印:
2019-03-26 15:10:06 106:開始
2019-03-26 15:10:06 106:我計算了一個值耗費200ms
2019-03-26 15:10:06 106:我做了一件耗費200ms的事情
2019-03-26 15:10:07 107:我做了一件耗費500ms的事情
2019-03-26 15:10:08 108:我做了一件耗費1000ms的事情
2019-03-26 15:10:08 108:最后得到的結果1203
2019-03-26 15:10:08 108:結束
同步執(zhí)行的時候,需要逐一等待所有的方法執(zhí)行完成,花費的時間顯然是所有的方法耗費的時間之和。

對于以上四個方法,如果使用異步的方式來執(zhí)行,將會很大程度的節(jié)省程序的運行時間,修改方法如下:
static async Task<int> AsyncMethod1()
{
await Task.Run(()=> {
Thread.Sleep(200);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:我計算了一個值耗費200ms");
});
return 1;
}
static async Task<int> AsyncMethod200ms()
{
await Task.Run(() => {
Thread.Sleep(200);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:我做了一件耗費200ms的事情");
});
return 200;
}
static async Task<int> AsyncMethod500ms(int index)
{
await Task.Run(() => {
Thread.Sleep(500);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:我做了一件耗費500ms的事情");
});
return ++index;
}
static async Task<int> AsyncMethod1000ms()
{
await Task.Run(() => {
Thread.Sleep(1000);
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:我做了一件耗費1000ms的事情");
});
return 1000;
}
使用異步的方式來調用方法:
static void Main(string[] args)
{
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:開始");
var m1 = AsyncMethod1();
var m2 = AsyncMethod200ms();
var m4 = AsyncMethod1000ms();
m1.Wait();
var m3 = AsyncMethod500ms(m1.Result);
m2.Wait();
m3.Wait();
m4.Wait();
var result = m1.Result + m2.Result + m3.Result + m4.Result;
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:最后得到的結果{result}");
Console.WriteLine($"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss ms")}:結束");
Console.ReadKey();
}
控制臺打印:
2019-03-26 14:11:54 1154:開始
2019-03-26 14:11:54 1154:我計算了一個值耗費200ms
2019-03-26 14:11:54 1154:我做了一件耗費200ms的事情
2019-03-26 14:11:55 1155:我做了一件耗費500ms的事情
2019-03-26 14:11:55 1155:我做了一件耗費1000ms的事情
2019-03-26 14:11:55 1155:最后得到的結果1203
2019-03-26 14:11:55 1155:結束
因為 AsyncMethod500ms() 依賴于 AsyncMethod1() 的返回結果作為參數(shù),所以我們可以先直接以異步的方式運行 AsyncMethod1() , AsyncMethod200ms() , AsyncMethod1000ms() 三個方法,這個時候三個方法都會建立異步的子線程進行執(zhí)行,但是后面的 AsyncMethod500ms() 想要執(zhí)行,必須的有 AsyncMethod1() 的返回值,所以這個時候對 AsyncMethod1() 進行等待, 200ms 后, AsyncMethod1() 執(zhí)行完成, m1.Wait() 等待結束,繼續(xù)執(zhí)行 AsyncMethod500ms() ,并傳入了 AsyncMethod1() 的返回值 m1.Result ,最后因為需要對四個方法的返回值進行累加,所以在這之前必須保證其它三個方法也執(zhí)行完成,所以需要分別對 AsyncMethod500ms() , AsyncMethod200ms() , AsyncMethod1000ms() 進行等待(Wait),因為此刻所有的方法都是異步執(zhí)行的,所以程序的執(zhí)行時間將≈執(zhí)行時間最長的那個方法的執(zhí)行時間( AsyncMethod1000ms() 執(zhí)行 1000ms ,執(zhí)行時間最長,程序的執(zhí)行時間≈ 1000ms )。

看完上面的內容,可以確定的是,在某些情況下,異步編程能夠很大的提高我們程序運行的效率,但是大家都在推崇的多使用異步編程不僅僅是因為軟件上面的原因,在硬件上也有著很大的原因。
前段時間我們將原來跑在一臺辦公電腦的程序發(fā)布到一臺雙路E5的DELL的刀片機上面去,結果發(fā)現(xiàn)在DELL刀片機上面運行的性能竟然比之前的辦公電腦還差,開始我們懷疑是DELL刀片機使用的是虛擬機的問題,可能在某些地方沒有設置好,后來經過一系列的服務器性能測試,無論是CPU處理速度、磁盤IO還是網絡帶寬,DELL刀片機都遠超我們之前的那臺辦公電腦,但是我們運行的程序中的某個接口在效率上就是不如之前的辦公電腦?。。?/p>
???(直到后來的某一天,隨著我對.NET Core異步編程的理解的加深,終于明白是什么原因。)
我們先來看一下我們日常開發(fā)使用的Intel CPU和服務器使用的CPU對比
開發(fā)電腦CPU: 英特爾® 酷睿™ i5+8500 處理器
- 處理器基本頻率:3GHz
- 最大睿頻頻率:4.1GHz
- 內核數(shù):六核心
- 線程數(shù):六線程
服務器CPU: 英特爾® 至強® D-2177NT 處理器
- 處理器基本頻率:1.90 GHz
- 最大睿頻頻率:3.00 GHz
- 內核數(shù):14
- 線程數(shù):28
從上面的對比我們可以發(fā)現(xiàn)兩者之間的差異很明顯, i5 處理器的基本頻率和最大睿頻都高于服務器使用的 至強 處理器,但是在內核數(shù)量和線程數(shù)量上面卻遠遠不如 至強 ,如果我們的程序全部使用的同步編程的話,以WebApi為例,每一次請求中調用的方法都只是在CPU的某一個內核/線程中進行的,換句話說,CPU單核頻率的高低直接影響著同步方法的執(zhí)行效率,而我們之前的程序幾乎都是使用了同步方法,在辦公電腦上的 i5 處理器和服務器使用的 至強 處理器的單核頻率的差異顯然就是之前性能問題的直接原因。
并且鑒于服務器CPU的特性(單核頻率低,內核/線程數(shù)多),在程序中多使用異步/多線程的方式對于程序的性能而言是無容置疑的。
注意:
雖然異步編程很多時候能提升程序的效率,但不并意味著需要為了使用異步而將所有的方法改為異步執(zhí)行,如果同步執(zhí)行的開銷甚至比創(chuàng)建一個異步線程開銷還低的時候,就完全沒有必要再此處使用異步的方式。至于這其中的權衡利弊,或許需要一定的經驗才能拿捏的住。
以上就是我目前對.NET Core中使用異步/多線程編程方式的理解, 希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
ASP.NET?Core?MVC創(chuàng)建控制器與依賴注入講解
這篇文章介紹了ASP.NET?Core?MVC創(chuàng)建控制器與依賴注入,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-02-02
ASP.NET網站模板的實現(xiàn)(第2節(jié))
這篇文章主要為大家詳細介紹了如何實現(xiàn)網站模板,了解母版頁在整合頁面公共元素、統(tǒng)一頁面風格中的作用,感興趣的朋友可以參考下2015-08-08
關于有些Asp.net項目發(fā)布后出現(xiàn)網址亂碼的解決方法
最近在部署一個網站,net2.0開發(fā)的,但是遇到一個很奇怪的問題。2011-07-07
.NET?Core使用?CancellationToken?取消API請求的操作方法
用戶取消請求時,你可以使用HttpContext.RequestAborted訪問,您也可以使用依賴注入將其自動注入到您的操作中,這篇文章主要介紹了.NET?Core使用?CancellationToken?取消API請求,需要的朋友可以參考下2024-03-03
.NET微信開發(fā)之PC 端微信掃碼注冊和登錄功能實現(xiàn)
這篇文章主要介紹了.NET微信開發(fā)之PC 端微信掃碼注冊和登錄功能實現(xiàn)的相關資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2016-09-09

