異步編程

er74 9年前發布 | 18K 次閱讀 異步編程

概念

異步編程核心為異步操作,該操作一旦啟動將在一段時間內完成。所謂異步,關鍵是實現了兩點:(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

 本文由用戶 er74 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!