異步編程
概念
異步編程核心為異步操作,該操作一旦啟動將在一段時間內完成。所謂異步,關鍵是實現了兩點:(1)正在執行的此操作,不會阻塞原來的線程(2)一旦啟動的此操作,可以繼續執行其他任務。當該操作完成時,將調用回調函數來通知該操作已經結束。
【注】:本人一直以為同步和異步都屬于多線程的范疇,到今天才明白完全錯誤,異步和多線程是屬于不同范疇,多線程和異步是并發的兩種形式,并行處理和線程同步是多線程的兩種形式,這是我當前的理解,不知是否有誤,文中若有錯誤,請園友拍磚并指正,初次學習難免有誤,望海涵!
那么問題來了,為什么說異步編程高效呢?首先得了解"IO操作的DMA(Direct Memory Access)模式"即直接內存訪問,是一種不經過CPU而直接經過內存數據存儲的數據交換模式。通過DMA的數據交換幾乎可以不損耗CPU的資源。而 CLR提供的異步編程模型正是充分利用了硬件的DMA功能來緩解CPU的壓力。
通過async和await關鍵字實現異步編程
一般使用方法:在方法聲明上加上async關鍵字,它的目的是使得方法內的關鍵字await生效(為了保持向后兼容,同時引入了這兩個關鍵字),如果async方法有返回值,返回Task<T>,若沒有則返回Task,返回這些Task的目的是通知主程序異步方法的結束。下面我們通過這兩個關鍵字用例子來介紹幾本用法!
async Task DoSomethingAsync() { int val = 13; // 異步方式等待1 秒 await Task.Delay(TimeSpan.FromSeconds(1)); val *= 2; // 異步方式等待1 秒 await Task.Delay(TimeSpan.FromSeconds(1)); Trace.WriteLine(val); }
在winform中程序中,添加一個按鈕和label文本,此按鈕的點擊事件代碼為:
1 private void btnSync_Click(object sender, EventArgs e) 2 { 3 DoSomethingSync(); 4 label1.Text = "Async Done"; 5 }
【問題1】當點擊此按鈕時先執行完異步方法后輸出26,再執行label1.Text=“Aysnc Done”?答案是NO!因為async在開始時是以同步方式執行 ,在其方法內部由于await關鍵字的存在則會執行一個異步等待!但是在此之前,它首先檢查該操作是否已經完成,若完成,則繼續以同步方式繼續運行!否則則會暫停異步方法,并返回,遺留下這個未完成的Task。一段時間后操作完成,該異步方法恢復運行!是不是沒太理解?通俗點說就是,當觸發點擊事件時,先執行異步方法,此時會在線程池中新起一個工作線程,但是不會阻塞主線程的運行,所以此時會返回一個異步方法中遺留的而未完成的任務,先執行下面一句 label1.Text="Async Done",直到該任務完成輸出26!
【問題2】如果將上述事件寫成如下,結果又會怎樣??結論就是先執行完異步方法輸出26,然后再執行label1.Text=“Aysnc Done!
private async void btnSync_Click(object sender, EventArgs e) { await DoSomethingSync(); label1.Text = "Async Done"; }
這個相當于async嵌套了,點擊觸發該異步事件,執行異步方法DoSomethinSync,所以會新起一個工作線程,不影響主線程的運行,但是此時主線程就是該異步事件,則先執行完該方法后輸出26再執行輸出文本label1.Text = "Async Done";
所以通過上面得出await關鍵字的作用:在線程池中新起一個將被執行的工作線程Task,當要執行IO操作時則會將工作線程歸還給線程池,因此await所在的方法不會被阻塞。當此任務完成后將會執行該關鍵字之后代碼!
關于異步方法async
一個async方法是有多個同步執行的程序塊組成,每個同步執行的的程序塊由await隔離!所以鑒于此,每個同步程序塊都會試圖在原始的上下文中恢復運行,也就是說如果UI線程中調用上述DoSomethinSync方法,則會在UI線程中運行,如果在線程池中調用,則會在線程池的線程中運行!這當然不是我們想要的結果,我們需要的是在調用的線程中運行,同時也要避免這種錯誤的行為。
對于上述異步方法在異步等待中只需這樣修改即可 Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); 對于ConfigureAwait為true時就是將你調用的方法返回到原始的上下文中運行!如此設置后這將在調用的線程中運行。
關于任務Task
(1)需要CPU實際執行命令,創建此類計算的任務時,使用Task.Run,若需按照特定的計劃進行則用TaskFactory.StartNew
(2)需要基于通知(notification)事件的任務和大部分需要IO操作時,使用TaskCompletionSource<T>
總結
(1)在異步編程需要注意的地方
【1】如果使用了async異步方法最好就一直使用它,再調用返回結束返回的Task對象避免使用Task.Wait或者Task<T>.Result方法,因為極容易造成死鎖。
【2】不要用void 作為async 方法的返回類型! async 方法可以返回void,但是這僅限于編寫事件處理程序。一個普通的async方法如果有返回值返回Task<T>,如果沒有返回值,要返回Task,而不是void!
【3】在核心庫代碼中一直使用ConfigureAwait。在外圍的用戶界面代碼中,只在需要時才恢復上下文。
(2)參考資料
【1】C#并發編程經典實例
【2】園子一位園友在C#并發編程經典實例上的標注,實在找不到園友鏈接了,在此表示感謝。
來自:http://www.cnblogs.com/CreateMyself/p/4677150.html