先來看一下.NET Doc的說明
" C# 5 引進了一種簡化的方法 (非同步程式設計)
來運用 .NET Framework 4.5 和更新版本
.NET Core 以及 Windows 執行階段中的非同步支援 "
而async/await主要是有關I/O bound的非同步處理
C#本身提供的API也加入了許多支援非同步處理的方法:
而使用async/await撰寫非同步處理會變得相對容易
但一般網路上找不太到前後的比對
到底是變得多容易?而中間的差異到底是什麼?
而必須再強調一點
async/await是用來處理有關I/O bound的非同步作業
先看看原本的做法如下:
在Main裡面一開始先啟動stopwatch計時器
接著呼叫一個HttpGetStringLength的方法
裡面就用正常同步的方式去呼叫GetStringAsync().Result
然後把字串長度印出來
這邊在印Console時另外實作一個ConsoleMessage的方法
順帶把正在執行的thread的id與程式執行時間印出來
得到結果如下:
這個結果很正常,就是一個執行緒跑到完
如果把HttpGetStringLength改成非同步的做法
HttpGetStringLengthAsync方法裡面在HttpClinet.GetStringAsync後
採用await的方式,非同步等待
但由於HttpGetStringLengthAsync這個方法是async Task
所以VS2017會有提示訊息
告訴你說,Main在呼叫到HttpGetStringLengthAsync這個方法時
不會等候此呼叫,會直接往下執行
那接著看印出的結果如下:
這個結果在一般的多執行緒的概念來說蠻怪的
可能很多人會以為非同步就會是HttpGetStringLengthAsync這個方法都是另外的執行緒在執行
但thread 1一直執行到印出HttpGetStringLengthAsync in
之後會回到Main function繼續往下印出Main back
而在HttpGetStringLengthAsync方法的await getStringTask後
就是另外一個執行緒thread 9在執行
這奇怪的地方在於,執行緒的斷點會在方法之中!!
這如果要用過去的方式來理解,第一個想到的方式就是callback機制
非同步的實作方式可能會像這樣:
就是在處理http client的連線時,用一個Func<string>委派function來執行
以及實作一個AsyncCallBack委派asyncCallback
(實作callback方法OnWorkCallBack引數為IAsyncResult)
並且採用BeginInvoke的方式非同步呼叫,並帶入委派實體:asyncCallback, function
OnWorkCallBack方法中先取出傳入的委派function
再利用EndInvoke取出function的結果
其印出的結果如下:
多印了一個在Func<string>裡面的資訊
可以清楚看到從Func<string>裡面開始就是由thread 3處理
一直到callback方法OnWorkCallback都是thread 3
後來有了Task.Run非同步作業機制,也可以寫成如下方式
類似callback的方式來實作後續要做的事情
印出的結果如下:
目前提了兩種callback的非同步實作方式
只是方便理解async/await的執行緒運作"流程"
過去可能會以為這樣運作的非同步效果是一樣的
但實際上在最後httpClient.GetStringAsync的運作上還是有所不同
string getString = httpClient.GetStringAsync("https://google.com.tw").Result;
以及
string getString = await httpClient.GetStringAsync("https://google.com.tw");
這兩行程式碼的運作機制是不一樣的!
async/await要解決的主要是I/O bound的非同步
而非兩個不同cpu bound的非同步
像第一個單執行緒的範例一樣
由於httpClient.GetStringAsync為I/O動作,速度較慢
這個thread 1會持續等待httpClient.GetStringAsync的回傳值
這個等待的過程中,thread 1是被占用而浪費的
而C#在提供Async結尾的方法時
就是進行I/O處理的優化,並且在I/O處理時不佔用執行緒
就是第二種採用await的方式
其乍看之下好像流程跟後來用callback實作的方式很像
無論是BeginInvoke或是Task.Run,在task in跟task getString都還是thread 3 !!
其中關鍵的點就是在await這行程式
採用await方式去呼叫Async的方法
其所謂的非同步的意思應該是:
原本執行的thread不等待I/O,同時間I/O開始進行工作
I/O工作結束後,會再找一個新的thread來接續await後面的工作
這之間不同的點如下圖:
第一種,在執行到await後,I/O開始作業
而thread 1會回到Main繼續往下執行
而用BeginInvoke或Task.ContinueWith,則是產生一個新的thread去執行I/O
thread 1回到Main繼續往下執行
在C#後來所提供有Async結尾的方法,如果你採用await的方式
執行緒的利用會如上圖的第一條長條圖,中間的I/O處理,其實是不佔用執行緒的
在I/O這段時間內,整個程式還是只有thread 1在執行
而透過BeginInvoke或Task.Run的方式,雖然也達成了非同步處理
但由於在I/O這段非採用await的方式,則會讓產生的新thread去等待I/O完成
在I/O這段時間內,整個程式有thread 1持續執行,而thread 3在等待中
所以這段時間,thread 3的執行效率其實是浪費掉的
這對有大量I/O動作(資料庫存取、檔案讀寫、網路傳輸)的程式來說
C#在底層提供這樣的非同步I/O機制
能大幅減低CPU執行緒的占用,避免不必要的等待浪費
這也是有些人會認為async/await是C#在導入lambda語法後最重要的功能了!!
以上是針對非Web API、Windows Form、WPF的運作狀況做討論
上述的thread流程又會稍微不同 XDD
但整體而言
async/await為非同步I/O作業
這樣的稱呼可能比較容易理解
而其不占用thread的方式,也可以解決Web API或windows UI被lock的問題
以上做點紀錄幫自己釐清有關async/await的運作
" C# 5 引進了一種簡化的方法 (非同步程式設計)
來運用 .NET Framework 4.5 和更新版本
.NET Core 以及 Windows 執行階段中的非同步支援 "
而async/await主要是有關I/O bound的非同步處理
C#本身提供的API也加入了許多支援非同步處理的方法:
而使用async/await撰寫非同步處理會變得相對容易
但一般網路上找不太到前後的比對
到底是變得多容易?而中間的差異到底是什麼?
而必須再強調一點
async/await是用來處理有關I/O bound的非同步作業
先看看原本的做法如下:
在Main裡面一開始先啟動stopwatch計時器
接著呼叫一個HttpGetStringLength的方法
裡面就用正常同步的方式去呼叫GetStringAsync().Result
然後把字串長度印出來
這邊在印Console時另外實作一個ConsoleMessage的方法
順帶把正在執行的thread的id與程式執行時間印出來
得到結果如下:
這個結果很正常,就是一個執行緒跑到完
如果把HttpGetStringLength改成非同步的做法
HttpGetStringLengthAsync方法裡面在HttpClinet.GetStringAsync後
採用await的方式,非同步等待
但由於HttpGetStringLengthAsync這個方法是async Task
所以VS2017會有提示訊息
告訴你說,Main在呼叫到HttpGetStringLengthAsync這個方法時
不會等候此呼叫,會直接往下執行
那接著看印出的結果如下:
這個結果在一般的多執行緒的概念來說蠻怪的
可能很多人會以為非同步就會是HttpGetStringLengthAsync這個方法都是另外的執行緒在執行
但thread 1一直執行到印出HttpGetStringLengthAsync in
之後會回到Main function繼續往下印出Main back
而在HttpGetStringLengthAsync方法的await getStringTask後
就是另外一個執行緒thread 9在執行
這奇怪的地方在於,執行緒的斷點會在方法之中!!
這如果要用過去的方式來理解,第一個想到的方式就是callback機制
非同步的實作方式可能會像這樣:
就是在處理http client的連線時,用一個Func<string>委派function來執行
以及實作一個AsyncCallBack委派asyncCallback
(實作callback方法OnWorkCallBack引數為IAsyncResult)
並且採用BeginInvoke的方式非同步呼叫,並帶入委派實體:asyncCallback, function
OnWorkCallBack方法中先取出傳入的委派function
再利用EndInvoke取出function的結果
其印出的結果如下:
多印了一個在Func<string>裡面的資訊
可以清楚看到從Func<string>裡面開始就是由thread 3處理
一直到callback方法OnWorkCallback都是thread 3
後來有了Task.Run非同步作業機制,也可以寫成如下方式
類似callback的方式來實作後續要做的事情
印出的結果如下:
目前提了兩種callback的非同步實作方式
只是方便理解async/await的執行緒運作"流程"
過去可能會以為這樣運作的非同步效果是一樣的
但實際上在最後httpClient.GetStringAsync的運作上還是有所不同
string getString = httpClient.GetStringAsync("https://google.com.tw").Result;
以及
string getString = await httpClient.GetStringAsync("https://google.com.tw");
這兩行程式碼的運作機制是不一樣的!
async/await要解決的主要是I/O bound的非同步
而非兩個不同cpu bound的非同步
像第一個單執行緒的範例一樣
由於httpClient.GetStringAsync為I/O動作,速度較慢
這個thread 1會持續等待httpClient.GetStringAsync的回傳值
這個等待的過程中,thread 1是被占用而浪費的
而C#在提供Async結尾的方法時
就是進行I/O處理的優化,並且在I/O處理時不佔用執行緒
就是第二種採用await的方式
在await後面直接印出getString.Length是thread 9
無論是BeginInvoke或是Task.Run,在task in跟task getString都還是thread 3 !!
其中關鍵的點就是在await這行程式
採用await方式去呼叫Async的方法
其所謂的非同步的意思應該是:
原本執行的thread不等待I/O,同時間I/O開始進行工作
I/O工作結束後,會再找一個新的thread來接續await後面的工作
這之間不同的點如下圖:
第一種,在執行到await後,I/O開始作業
而thread 1會回到Main繼續往下執行
而用BeginInvoke或Task.ContinueWith,則是產生一個新的thread去執行I/O
thread 1回到Main繼續往下執行
在C#後來所提供有Async結尾的方法,如果你採用await的方式
執行緒的利用會如上圖的第一條長條圖,中間的I/O處理,其實是不佔用執行緒的
在I/O這段時間內,整個程式還是只有thread 1在執行
而透過BeginInvoke或Task.Run的方式,雖然也達成了非同步處理
但由於在I/O這段非採用await的方式,則會讓產生的新thread去等待I/O完成
在I/O這段時間內,整個程式有thread 1持續執行,而thread 3在等待中
所以這段時間,thread 3的執行效率其實是浪費掉的
這對有大量I/O動作(資料庫存取、檔案讀寫、網路傳輸)的程式來說
C#在底層提供這樣的非同步I/O機制
能大幅減低CPU執行緒的占用,避免不必要的等待浪費
這也是有些人會認為async/await是C#在導入lambda語法後最重要的功能了!!
以上是針對非Web API、Windows Form、WPF的運作狀況做討論
上述的thread流程又會稍微不同 XDD
但整體而言
async/await為非同步I/O作業
這樣的稱呼可能比較容易理解
而其不占用thread的方式,也可以解決Web API或windows UI被lock的問題
以上做點紀錄幫自己釐清有關async/await的運作
您真是寫得太好了...QQ
回覆刪除謝謝~
刪除獲益匪淺
回覆刪除謝謝~
刪除好深奧
回覆刪除greate
回覆刪除