C#複習筆記(5)--C#5:簡化的非同步編程(非同步編程的基礎知識)

來源:互聯網
上載者:User

標籤:接下來   迴圈   completed   特定   暫停   詳細介紹   編程   c中   created   

非同步編程的基礎知識

C#5推出的async和await關鍵字使非同步編程從表面上來說變得簡單了許多,我們只需要瞭解不多的知識就可以編寫出有效非同步代碼。

在介紹async和await之前,先介紹一些基礎的概念:

並發:同時做很多事情。

這個解釋直接表明了並發的作用。終端使用者程式利用並發功能,在輸入資料庫的同時響應使用者輸入。伺服器應用利用並發,在處理第一個請求的同時響應第二個請求。只要你希望程式同時做多件事情,你就需要並發。幾乎每個軟體程式 都會受益於並發。

多線程:並發的一種形式,它採用多個線程來執行程式。

從字面上看,多線程就是使用多個線程。本書後續章節將介紹,多線程是並發的一種形式,但不是唯一的形式。實際上,直接使用底層線程類型在現代程式中基本不起作用。 比起老式的多線程機制,採用進階的抽象機制會讓程式功能更加強大、效率更高。因此,本書將盡量不涉及一些過時的技術。書中所有多線程的方法都採用進階類型,而不是Thread或BackgroundWorker。但是,不要認為多線程已經徹底被淘汰了!因為線程池要求多線程繼續存在。線程池存放任務的隊列,這個隊列能夠根據需要自行調整。相應地,線程池產生了另一個重要的並發形式:平行處理。

平行處理:把正在執行的大量的任務分割成小塊,分配給多個同時啟動並執行線程。

為了讓處理器的利用效率最大化,平行處理(或並行編程)採用多線程。當現代多核CPU執行大量任務時,若只用一個核執行所有任務,而其他核保持空閑,這顯然是不合理的。平行處理把任務分割成小塊並分配給多個線程,讓它們在不同的核上獨立運行。平行處理是多線程的一種,而多線程是並發的一種。在現代程式中,還有一種非常重要但很多人還不熟悉的並發類型:非同步編程。

非同步編程:並發的一種形式,它採用future模式或回調(callback)機制,以避免產生不必要的線程。

一個future(或promise)類型代表一些即將完成的操作。在.NET中,新版future類型有Task和Task<TResult>。在老式非同步編程API中,採用回調或事件(event),而不是future。非同步編程的核心理念是非同步作業:啟動了的操作將會在一段時間後完成。這個操作正在執行時,不會阻塞原來的線程。啟動了這個操作的線程,可以繼續執行其他任務。當操作完成時,會通知它的future,或者調用回呼函數,以便讓程式知道操作已經結束。非同步編程是一種功能強大的並發形式,但直至不久前,實現非同步編程仍需要特別複雜的代碼。VS2012支援async和await,這讓非同步編程變得幾乎和同步(非並發)編程一樣容易。

非同步編程簡介非同步編程的執行流程

現代的非同步.NET程式使用兩個關鍵字:async和await。async關鍵字加在方法聲明上,它的主要目的是使方法內的await關鍵字生效(為了保持向後相容,同時引入了這兩個關鍵字)。如果async方法有傳回值,應返回Task<T>;如果沒有傳回值,應返回Task。這些task類型相當於future,用來在非同步方法呼叫結束時通知主程式。還有一種是返回void,這種就是存粹的為了相容事件處理常式。所以,除了用於註冊實踐處理常式,不建議在別的地方使用返回void的非同步代碼。

先來看一個例子:

 async Task AsyncMethod()        {            Console.WriteLine("Sync execute before await");//①同步執行的代碼            await Task.Delay(TimeSpan.FromSeconds(5));//②非同步等待,非阻塞            Console.WriteLine("callback method");//③任務的延續        }

 

和其他方法一樣,async方法在開始時以同步方式執行①。在async方法內部,await關鍵字對它的參數執行一個非同步等待②。它首先檢查操作是否已經完成,如果完成了,就繼續運行(同步方式)。否則,它會暫停async方法,並返回,留下一個未完成的task(token)。一段時間後,操作完成,async方法就恢複運行③。就是這麼簡單。編譯器在後面協助我們做了大量的工作。在後續的章節中,會詳細介紹編譯器的所作所為。

同步上下文:一個async方法是由多個同步執行的程式塊組成的,每個同步程式塊之間由await語句分隔②。第一個同步程式塊在調用這個方法的線程中運行,但其他同步程式塊在哪裡運行呢?情況比較複雜。最常見的情況是,用await語句等待一個任務完成,當該方法在await處暫停時,就可以捕捉上下文(context)。如果當前SynchronizationContext不為空白,這個上下文就是當前SynchronizationContext。如果當前SynchronizationContext為空白,則這個上下文為當前TaskScheduler。該方法會在這個上下文中繼續運行③。一般來說,運行UI線程時採用UI上下文,處理ASP.NET請求時採用ASP.NET請求上下文,其他很多情況下則採用線程池上下文。因此,在上面的代碼中,每個同步程式塊會試圖在原始的上下文中恢複運行。如果在UI線程中調用DoSomethingAsync,這個方法的每個同步程式塊都將在此UI線程上運行。但是,如果線上程池線程中調用,每個同步程式塊將線上程池線程上運行。要避免這種行為,可以在await中使用ConfigureAwait方法,將參數continueOnCapturedContext設為false。接下來的代碼剛開始會在調用的線程裡運行,在被await暫停後,則會線上程池線程裡繼續運行。

可等待模式:關鍵字await不僅能用於Task,還能用於所有遵循特定模式的awaitable類型——在編譯器產生的狀態機器中,有一個MoveNext的方法,在這個方法中會調用await的對象是否有一個GetAwaiter的方法,這個方法是否會返回一個awaiter,awaiter裡面是否包含了GetResult方法和IsCompleted屬性,awaiter遵循的介面是INotifyCompletion和ICriticalNotifyCompletion,從名字上面來看就知道他們的意思都是發送一個通知。類似的awaitable類型還有YieldAwaitable、ConfiguredTaskAwaitable,awaitable類型的一個特點是有一個GetAwaiter的方法來返回一個awaiter。

迭代器也是類似的原理,並且它出現的更早(C#2)。在foreach迴圈一個序列的時候,他不要求這個序列必須實現了IEnumerable或者IEnumerable<T>,foreach會尋找這個序列是否有一個GetEnumerator的方法,這個方法是否返回一個Enumerator,這個Enumerator是否包含一個MoveNext方法和一個返回當前元素的Current屬性。

處理異常

因為不確定Task的執行線程,所以Task不會主動的拋出異常,在返回的Task中的Status屬性上會指示Task的執行結果,Status屬性是TaskStatus枚舉類型,定義如下:

 public enum TaskStatus        {            Created,            WaitingForActivation,            WaitingToRun,            Running,            WaitingForChildrenToComplete,            RanToCompletion,            Canceled,            Faulted,        }

Task上面還有一個Exception屬性,類型是一個AggregateException。正常情況下,當Task執行順利完成時,Exception返回null。但Task執行失敗時,Exception屬性會返回一個AggregateException類型的異常,這個異常包含了Task執行過程中拋出的所有異常。發生異常時,任務結束,不直接拋出異常。只有在使用一個Task的時候,比如Task.Wait()、Task.WhenAll()、等等。還有,在await一個Task的時候,也會拋出異常,但是會拋出AggregateException中的第一個異常。

死結

關於非同步編程還有一個重要的準則就是,在有UI線程的地方,如果你要使用非同步編程,就要非同步到底,考慮下面的代碼:

 async Task WaitAsync()        {            //這裡awati會捕獲當前上下文……             await Task.Delay(TimeSpan.FromSeconds(1)); // ……這裡會試圖用上面捕獲的上下文繼續執行         }        void Deadlock()        {            //開始 延遲             Task task = WaitAsync(); //同步程式塊,正在等待非同步方法呼叫完成             task.Wait();        }

上面的代碼如果在UI線程或Asp.net 中執行,就會發生死結,來看看到底發生了什麼:這兩種上下文每次只能運行一個線程。Deadlock方法調用WaitAsync方法,WaitAsync方法開始調用delay語句。然後,Deadlock方法(同步)等待WaitAsync方法完成,同時阻塞了上下文線程。當delay語句結束時,await試圖在已捕獲的上下文中繼續運行WaitAsync方法,但這個步驟無法成功,因為上下文中已經有了一個阻塞的線程,並且這種上下文只允許同時運行一個線程。這裡有兩個方法可以避免死結:在WaitAsync中使用ConfigureAwait(false)(導致await忽略該方法的上下文),或者用await語句調用WaitAsync方法(讓Deadlock變成一個非同步方法呼叫)。

 

 

基礎的東西就這麼多,這個章節裡面沒有多少例子,不過如果能看懂裡面的所有意思,那麼編寫非同步編程也不是什麼難事。下面的章節會詳細的介紹編譯器為async和await所做的所有事情,並會將相關的概念進行進一步的擴充。

 

C#複習筆記(5)--C#5:簡化的非同步編程(非同步編程的基礎知識)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.