随着.NET Core的流行,相信你現在的代碼中或多或少的會用到 async await async await
以及
吧!畢竟已成标配。那麼我們為什麼要用
呢?其實這是微軟團隊為我們提供的一個文法糖,讓我們不用996就可以輕松的編寫異步代碼,并無太過神奇的地方。那麼,問題來了,什麼是異步?異步到底又是怎樣的一個過程呢?
從一個故事說起
在開始講異步前我們先從一個生活中的小故事說起吧。話說2019年12月15日周日這一天有位程式猿小祝在這天居然沒有加班,選擇在家休息了,然後他習慣性的用
Microsoft To Do
羅列了一下這天要做的事情,如下圖所示:

這一天這個程式猿小祝計劃早上九點起床洗澡,然後吃早餐,洗衣服,分享一篇關于
C#異步
相關的文章,晚上在家加下班~~沒錯,這個苦逼休息的時候也得工作,不然下周的任務有可能完不成要挨批了。
這個時候這個程式猿小祝可以選擇,1.起床洗澡,2.吃早餐,3.洗衣服,4.寫文章,5.打會球然後“遠端寫代碼”。這個過程有嚴格的執行順序,這個過程可以視為一個同步的過程。如下圖所示:
當然,這個程式猿小祝卻采用了另一種方式來進行:起床後先把衣服換下來用洗衣機洗了,然後開始洗澡,然後吃飯,寫了一會文章,然後等衣服洗好後再把衣服給晾好繼續回來寫文章,最後在晚上的時候遠端寫代碼。在這個過程中這個程式猿在洗衣服的同時就去洗澡,吃飯寫了會文章了,這個過程就是一個異步的過程。
可能這個故事比喻的不恰當,不過大夥将就着看下吧,總結一下同步跟異步吧:
- 同步方法:可以認為程式是按照你寫這些代碼時所采用的順序執行相關的指令的。
- 異步方法:可以在尚未完成所有指令的時候提前傳回(如上面的洗衣服過程沒執行完就傳回去洗澡了),等到該方法等候的那項任務執行完畢後,在令這個方法從早前還沒執行完的那個地方繼續往下運作(如:衣服洗好晾好後,繼續寫文章了)。
下面我們結合僞代碼來進行更加詳細的講解吧。
僞代碼執行個體講解
這一節我們就用僞代碼來分别實作下同步過程及異步過程吧。
同步過程
下面我們用僞代碼來實作上述故事中的過程吧。
static void Main(string[] args)
{
Console.WriteLine("Main異步示範開始~~~~~");
Stopwatch stopwatch = Stopwatch.StartNew();
Bash();//洗澡
BreakFast();//吃早餐
WashClothes();//洗衣服
WriteArticle();//寫文章
WritingCode();//寫代碼
Console.WriteLine("Main異步示範結束~~~~~共用時{0}秒!", stopwatch.ElapsedMilliseconds/1000);
Console.ReadKey();
}
private static void Bash()
{
Console.WriteLine("洗澡開始~~~~~");
Thread.Sleep(1*1000);//模拟過程
Console.WriteLine("洗澡結束~~~~~");
}
private static void BreakFast()
{
Console.WriteLine("吃早餐開始~~~~~");
Thread.Sleep(1 * 1000);//模拟過程
Console.WriteLine("吃早餐結束~~~~~");
}
private static void WashClothes()
{
Console.WriteLine("洗衣服開始~~~~~");
Thread.Sleep(6 * 1000);//模拟過程
Console.WriteLine("洗衣服結束~~~~~");
}
private static void WriteArticle()
{
Console.WriteLine("寫文章開始~~~~~");
Thread.Sleep(20 * 1000);//模拟過程
Console.WriteLine("寫文章結束~~~~~");
}
private static void WritingCode()
{
Console.WriteLine("寫代碼開始~~~~~");
Thread.Sleep(12 * 1000);//模拟過程
Console.WriteLine("寫代碼結束~~~~~");
}
上面的代碼沒什麼難的,寫完代碼後我們直接
dotnet run
一下代碼,如下圖所示:
我們可以看到這個代碼的執行過程是嚴格按照我們編碼的順序執行的,即同步運作的代碼。這裡用時共40秒!
異步過程
我們隻需要稍微改造下使得代碼異步執行再來看下效果吧!僞代碼如下:
static async Task Main(string[] args)
{
Console.WriteLine("Main異步示範開始~~~~~");
Stopwatch stopwatch = Stopwatch.StartNew();
List<Task> tasks = new List<Task>
{
Bash(),//洗澡
};
tasks.Add(BreakFast());//吃早餐
tasks.Add(WashClothes());//洗衣服
tasks.Add(WriteArticle());//寫文章
tasks.Add(WritingCode());//寫代碼
await Task.WhenAll(tasks);
Console.WriteLine("Main異步示範結束~~~~~共用時{0}秒!", stopwatch.ElapsedMilliseconds/1000);
Console.ReadKey();
}
private static async Task Bash()
{
Console.WriteLine("洗澡開始~~~~~");
await Task.Delay(1*1000);//模拟過程
Console.WriteLine("洗澡結束~~~~~");
}
private static async Task BreakFast()
{
Console.WriteLine("吃早餐開始~~~~~");
await Task.Delay(1 * 1000);//模拟過程
Console.WriteLine("吃早餐結束~~~~~");
}
private static async Task WashClothes()
{
Console.WriteLine("洗衣服開始~~~~~");
await Task.Delay(6 * 1000);//模拟過程
Console.WriteLine("洗衣服結束~~~~~");
}
private static async Task WriteArticle()
{
Console.WriteLine("寫文章開始~~~~~");
await Task.Delay(20 * 1000);//模拟過程
Console.WriteLine("寫文章結束~~~~~");
}
private static async Task WritingCode()
{
Console.WriteLine("寫代碼開始~~~~~");
await Task.Delay(12 * 1000);//模拟過程
Console.WriteLine("寫代碼結束~~~~~");
}
然後我們再直接
dotnet run
我們可以看到這個代碼的執行過程中遇到
await
後就會傳回執行了,待await的代碼執行完畢後才繼續執行接下來的代碼的!為了避免有的讀者看不懂,我簡單分析其中一個方法的執行過程吧。具體的還需要你自己把異步代碼拷貝下來,多打幾個斷點,然後把等待時間*100(時間長點友善我們檢視斷點的進入順序,否則時間短,還沒來得及進斷點可能代碼已經執行完了)看看斷點的進入步驟吧!
我也隻列了一部分,具體的你們自行打斷點看下吧。
異步原了解析
通過上面的僞代碼分析相信你已經對異步有所了解了。接下來我們就來看看系統到底是怎麼實作出這樣的效果的。下面隻是簡單地進行下表述,如果不正确的歡迎大家指正。
編譯器在處理異步方法的時候,會建構一種機制,該機制可以啟動
await
語句所要等候的那項異步任務,并使得程式在該工作完成之後,能夠用某個線程繼續執行
await
語句後面的那些代碼。這個
await
語句正是關鍵所在。編譯器會建構相應的資料結構,并把
await
之後的指令表示成
delegate
,使得程式在處理完那項異步任務之後,能夠繼續執行下面的那些指令。編譯器會把目前方法中的每一個局部變量的值都儲存在這個資料結構中,并根據
await
語句所要等候的任務來配置相應的邏輯,讓程式能夠在該任務完成之後指派某個線程,從
await
語句的下一條指令開始繼續執行。實際上,這相當于編譯器生成了一個
delegate
,用以表示
await
語句之後的那些代碼,并寫入了相應的狀态資訊,用以確定
await
語句所等候的那項任務執行完畢以後這個
delegate
能夠正确的得到調用。
這使得該方法看上去好像是從早前暫停的地方繼續往下執行了,也就是所,系統會把狀态恢複到早前暫停的樣式,并且直接把程式中的某個線程放到适當的語句上,令其能夠繼續向下運作。
這個過程實際上是由
SynchronizationContext
類來實作的,該類用來保證異步方法能夠在它所等候的任務執行完畢時,從早前停下來的地方繼續往下運作,并確定該方法此時所處的環境與上下文能夠與當初的情況一樣。
總結
通過上面的講述我們可以知道通過
async
與
await
關鍵字寫出來的異步方法并沒有太過神奇的地方。隻不過編譯器會針對這種方法生成許多代碼,使得調用這個方法的主調方無需等待該方法完工,就可以繼續往下執行,并確定該方法所等候的那項任務在執行過程中發生的錯誤能夠适當的得到回報。這樣的好處是,如果異步方法執行到await語句時它所要等候的那項任務還沒有完成,那麼該方法的執行進度就會暫停在那裡,直到那項任務完成之後,才回繼續往下執行。
希望這篇文章對你有所幫助,當然光了解異步沒用,還要能夠高效的編寫異步代碼才行哦,接下來我會抽時間講講進行異步開發的一些建議。當然我以前也寫過相關的文章,你可以提前看下。同時歡迎大家加入.net core兩千人交流群637326624`交流。當然我不會告訴你,關注公衆号會第一時間收到文章推送。
很久沒寫文章了,生疏了後多,大家将就着看吧!
參考
《More Effective C#》機械工業出版社
依樂祝自己的了解
作者:依樂祝(祝雷)
出處:https://www.cnblogs.com/yilezhu
聯系:[email protected] .NET Core實戰項目交流群:637326624 微信:jkingzhu
本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。如有問題或建議,請多多賜教,非常感謝。