使用Async和Await進行異步編程(C#版 適用於VS2015) z


你可以使用異步編程來避免你的應用程序的性能瓶頸並且加強總體的響應。然而,用傳統的技術來寫異步應用是復雜的,同時編寫,調試和維護都很困難。

VS2012介紹了簡單的方法,那就是異步編程,它在.Net Framework 4.5和Windows 運行時提供了異步支持。編譯器做了開發者以前做的困難的工作,而且你的應用保持了類似於異步代碼的邏輯結構。結果,你輕易地就獲得了所有異步編程的優勢。

 
異步提升響應

異步對於可能阻塞的活動是至關重要的。例如當你的應用訪問Web的時候,訪問web資源有時有點慢或者延時,如果這樣一個活動在同步進程中阻塞了,整個應用就必須等待。在異步進程中,此應用可以繼續其他的工作,而不依賴於web資源直到這個可能阻塞的任務完成。

下表展示了異步編程提升響應的典型領域。陳列的來自Framework 4.5 和the Windows Runtime 的APIs包含了支持async編程的方法。
應用領域     包含異步方法的APIs
Web 訪問     HttpClient, SyndicationClient
處理文件     StorageFile, StreamWriter, StreamReader, XmlReader
處理圖片     MediaCapture, BitmapEncoder, BitmapDecoder
WCF編程     Synchronous and Asynchronous Operations

 

 

 

 

 

對於訪問UI線程的應用,異步被證明是特別有價值的,因為所有Ui相關的活動通常共享一個線程。如果同步應用的任何一個進程被阻塞了,那么所有進程都被阻塞了。屆時你的應用停止了響應,你可能推斷它出錯了,然而它卻僅僅在等待。

當你使用異步方法的時候,應用會繼續響應UI。你可以調整或者最小化窗口,或者如果你不想等待應用完成,那就關了它。

基於異步的方法相當於在設計異步操作時,向可供你選擇的選項增加了自動的傳輸裝置。那就是說,你以更少的付出卻獲得了所有傳統異步編程的好處。
異步方法更容易編寫

關鍵字async和await是異步編程的核心。通過使用這兩個關鍵字,可以使用.NET Framework 或者Windows Runtime的資源來創建異步方法,這就像創建同步方法一樣簡單。使用await和async定義的方法為異步方法。

 

下面是一個異步方法的例子。代碼中的所有你都應該看着很熟悉。
復制代碼

// 簽名中需要注意的三件事:
//  -方法有一個async修飾符.
//  - 返回值是 Task 或 Task<T>.
//    這里返回一個Task<int>,因為return語句返回int類型
//  - 方法名以 "Async"結尾。
async Task<int> AccessTheWebAsync()
{
    // 先要添加 System.Net.Http引用來聲明 client.
    HttpClient client = new HttpClient();

    // GetStringAsync 返回 Task<string>. 這意味着當你等待這個任務的時候,你將獲得一個字符串(urlContents)。
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");


    //這里你可以處理任務,它不依賴來自GetStringAsync的字符串
    DoIndependentWork();

    // await 操作符延緩了AccessTheWebAsync.
    //  - AccessTheWebAsync 直到 getStringTask完成才繼續執行。
    //  - 同時, 控制返回到 AccessTheWebAsync的調用者.
    //  - 當getStringTask完成時,控制恢復.
    //  - await 操作符然后檢索來自 getStringTask的字符串.
    string urlContents = await getStringTask;

    //  return 語句表明返回一個整數.
    return urlContents.Length;
}

復制代碼

如果AccessTheWebAsync在調用GetStringAsync 和等待完成之間沒有其他要處理的代碼,可以用下面一條單句簡化代碼。

string urlContents = await client.GetStringAsync();

下面總結了一些上面的異步方法的例子的特點:

    方法簽名包括async修飾符
    異步方法的命名,按照慣例,以“Async”后綴結尾
    返回類型只能是這三種:Task<TResult>,Task或void
    方法通常至少包括一個await表達式,await標記了一個點,這個點就是直到異步操作完成后異步方法才繼續執行。同時,方法是延遲的,控制返回到方法的調用者。

異步方法中發生什么?

理解異步編程最重要的事情是 控制流如何從一個方法移動到另一個方法。下圖帶你理解這個過程。

對應數字序號的解釋如下:

 

    事件句柄調用並等待AccessTheWebAsync 異步方法。
    AccessTheWebAsync 創建一個HttpClient實例,並調用GetStringAsync異步方法來下載website的內容,保存到字符串。
    GetStringAsync發生了一些事情,延遲了方法的進度。也許必須等待站點下載或者一些其他的阻塞活動。為避免阻塞資源,GetStringAsync轉讓控制權給它的調用者AccessTheWebAsync 。
    因為getStringTask還沒有被await,所以AccessTheWebAsync 可以不依賴GetStringAsync返回的最終結果繼續工作。這項工作是一個同步方法DoIndependentWork。
    DoIndependentWork是一個處理一些事情的同步方法,並且返回給它的調用者。
    AccessTheWebAsync 已經完成了它能做的事情,但getStringTask還沒有返回結果。AccessTheWebAsync 下一步想要計算並返回已經下載的字符串的長度,但是該方法直到有字符串時才能計算那個值。
    因此,AccessTheWebAsync 使用了一個await操作符來延遲它的進度,並且轉讓控制權給調用AccessTheWebAsync 的方法。AccessTheWebAsync 返回一個Task<int>給調用者。這個task代表一種產生一個整數結果的允諾,這個整數結果就是下載字符串的長度。
    【注意:如果GetStringAsync(並且從而getStringTask)在AccessTheWebAsync等待它之前完成,那么控制權保留在AccessTheWebAsync中。如果調用的異步過程(getStringTask)已經完成了,延遲和后來返回到AccessTheWebAsync的代價將會被浪費,因而AccessTheWebAsync沒必要等待最終的結果了。】
    在調用者內部(這個例子中的事件句柄),處理模式繼續執行。在等待異步返回的結果之前,調用者可能處理其他的工作而不依賴來自AccessTheWebAsync的結果,或者調用者可能會立即等待。事件句柄等待AccessTheWebAsync,AccessTheWebAsync等待GetStringAsync。
    GetStringAsync完成並產生了字符串結果。這個字符串結果可能沒有按你期望的那樣通過GetStringAsync的調用被返回。(記住步驟3中該方法已經返回了一個task。)相反,這個字符串被保存到代表這個方法完成的任務對象getStringTask中。Await操作符檢索來自getStringTask的結果。賦值語句將檢索的結果賦值給urlContent變量。
    當AccessTheWebAsync獲得字符串結果時,該方法可以計算這個字符串的長度。然后AccessTheWebAsync的工作也完成了,等待中的事件句柄可以恢復了。

API異步方法

你可能想知道在哪里找到支持異步的像GetStringAsync的方法。.NET Framework 4.5包含了許多對async和await有效的成員。你可以通過“Async”后綴和Task或Task<TResult>的返回類型來識別這些成員。比如,System.IO.Stream類中,在同步方法CopyTo, Read和 Write的旁邊包含了很多像CopyToAsync, ReadAsync和WriteAsync的方法。
線程

async方法規定為非阻塞操作。當等待的task運行的時候,async方法的await表達式不會阻塞當前線程。相反,該表達式注冊當前方法的剩余作為延續,並且返回控制權給async方法的調用者。

async和await關鍵字不會造成額外的線程被創建。async方法不會要求多線程是因為async方法沒有運行在它自己的線程上。該方法運行在當前的同步上下文上,只有該方法激活的時候才會在該線程上使用時間。你可以使用Task.Run()來將CPU受限的工作移動到后台線程中,但是后台線程不會幫助處理僅僅等待結果變成可利用的。
異步和等待  

可以查看我的這篇博客Async and Await 異步和等待。
返回類型和參數

請查看我的這篇博客Async and Await 異步和等待。
命名慣例

按照慣例,給方法添加async修飾符,給方法名追加“Async”后綴。
復雜案例

下面的代碼來自WPF應用的MainWindow.xaml.cs。

 
復制代碼

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

// 添加 System.Net.Http的using指令和引用;
using System.Net.Http;

namespace AsyncFirstExample
{
    public partial class MainWindow : Window
    {
        // 給事件句柄添加 async標記,為的是可在方法體內使用 await .
        private async void StartButton_Click(object sender, RoutedEventArgs e)
        {
            // 分別調用和await.
            //Task<int> getLengthTask = AccessTheWebAsync();
            //// 可以在這里做一些獨立的工作.
            //int contentLength = await getLengthTask;

            int contentLength = await AccessTheWebAsync();

            resultsTextBox.Text +=
                String.Format("\r\nLength of the downloaded string: {0}.\r\n", contentLength);
        }

        // 簽名中需要注意的三件事:
        //  -方法有一個async修飾符.
        //  - 返回值是 Task 或 Task<T>.
        //    這里返回一個Task<int>,因為return語句返回int類型
        //  - 方法名以 "Async"結尾。
        async Task<int> AccessTheWebAsync()
        {
            // 先要添加 System.Net.Http引用來聲明 client.
            HttpClient client = new HttpClient();

            // GetStringAsync 返回 Task<string>. 這意味着當你等待這個任務的時候,你將獲得一個字符串(urlContents)。
            Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");


            //這里你可以處理任務,它不依賴來自GetStringAsync的字符串
            DoIndependentWork();

            // await 操作符延緩了AccessTheWebAsync.
            //  - AccessTheWebAsync 直到 getStringTask完成才繼續執行。
            //  - 同時, 控制返回到 AccessTheWebAsync的調用者.
            //  - 當getStringTask完成時,控制恢復.
            //  - await 操作符然后檢索來自 getStringTask的字符串.
            string urlContents = await getStringTask;

            //  return 語句表明返回一個整數.
            return urlContents.Length;
        }

        void DoIndependentWork()
        {
            resultsTextBox.Text += "Working . . . . . . .\r\n";
        }
    }
}

復制代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM