await和async更多的理解

来源:http://www.cnblogs.com/laogu2/archive/2017/10/27/7744411.html
-Advertisement-
Play Games

最近有不少網友提起await和async,呵呵,C# 5引進的語法糖。 這個語法糖還真不好吃,能繞倒一堆初學的朋友,在網上也有很多網友關於這塊知識點的爭論,有對有錯,今天在這裡把這個誤區好好講講。 在await(C# 參考)這樣寫道: “await 運算符應用於非同步方法中的任務,在方法的執行中插入掛 ...


最近有不少網友提起await和async,呵呵,C# 5引進的語法糖。

這個語法糖還真不好吃,能繞倒一堆初學的朋友,在網上也有很多網友關於這塊知識點的爭論,有對有錯,今天在這裡把這個誤區好好講講。

await(C# 參考)這樣寫道

“await 運算符應用於非同步方法中的任務,在方法的執行中插入掛起點,直到所等待的任務完成。 任務表示正在進行的工作。”


不要小看這兩句話,內容里暗指的意思還真不少。

1)await 運算符針對於非同步方法

2)await 插入掛起點

3)await 等待任務完成

4)任務表式正在進行的工作

帶著上面四點,我們暫時停下,因為提到await不禁要聯想到一個好基友aysnc

await(C# 參考)這樣寫道

"await 僅可用於由 async 關鍵字修改的非同步方法中"


到這裡,我們對上面的四個關鍵點,提出疑問。

await 運算符針對於非同步方法,那如果我們在非同步方法里添加入同步方法會怎麼樣?

  private static async Task<TResult> XAsync()
  {
           
     X(); //X()同步方法
        
     return await XXAsync(); //XXAsync()非同步方法
  }

 

然後在mainTest主線程里調用這個XAsync()方法

static void Main(string[] args)
{
   XAsync();

OtherMethod(); }

 

 

在main方法里,網上有網友博客說道:

 

1)XAsync在主線程里不會被調用,直到 awiat XAsync 才會被成功調用。就像linq to sql表達式一像,首先是var results=array.select().where();語句一樣,他們只是組裝,
並不會執行,要等到foreach(var result in results){ ...}迭代時或者.toList()再真正的查詢。

2)XAsync在主線程里會被調用,並不阻止主線程,主線程將繼續執行下麵的程式!原因是我們寫這個方法時候,vs會給我們警告,有圖為據

到底是誰正確呢?

呵呵,其實這裡兩種說法都不正確。

首先,XAsync()在主線程里,直接調用,肯定執行,VS此時也瞎胡鬧,警告我們說,“不等待此方法”,這是有個大大的前提!那就是這個方法體內,必須是非同步的!
可能說到此不好理解。

在XAsync()方法里,上面有一段同步方法X(),此時它是運行在主線程上的同步方法,會阻止主線程,在X()運行完全後,在運行至 return await XXAsync()時,才把主線程調用權交還給調用的方法

在此過程里,並不會產生新的線程,全部運行在主線程上

呵呵,越說越迷糊了。。。

await和async 講白了點,他們並不會真正意義上的去產生新的線程,我們都知道,產生線程可以用Task類或Thread類。
那麼async 標註的方法到底是什麼呢?微軟給我們的一句單簡的話,"await 僅可用於由 async 關鍵字修改的非同步方法中"

這就說明,async是為了await起到一種“配合”作用。而是說async修飾的方法都是非同步的,那也太相當然了。

在同步方法里,執行具有async修飾的方法,按同步方法來執行。也就是說X()是運行在主線程上的方法。

至於X()方法體內至於執行同步還是非同步決定權由方法體內的方法是否據有非同步功能!
就像我們上面一樣,XAsync()方法體內的X()就同步,那麼X()執行的依然是同步方法一樣,而不是運行另外一線程上。

那麼問題來了,那我們都直接X()方法多好,await還有何用,或者await 為何不直接調用下X()方法呢?。。。

於是我們繼續看下一句XXAsync()方法,我們既然用了await 語法,為什麼他可以用?我可以把他去了await嗎?

當然是肯定的,就像這樣:

private static async Task<TResult> XAsync()
  {
           
     X(); //X()同步方法0
        
     XXAsync();//"非同步方法1"

     return await XXXAsync(); //XXAsync()非同步方法2
  }


XXAsync() 此時是如何運行的呢?同步還是非同步?問題又返回來了,至於同步還是非同步,不是這個方法“名詞”決定的,而是這個方法體內是如何執行的

如:


 private static async Task XXAsync()
  {  
     X();  
  }

 

此時像上面調用的方式,XXAsync()就是我們平時的同步方法嘛!

但是改下:

private static async Task XXAsync()
  {  
     Task.Run(() =>{
             X();   
      });
  }


依據用相同的方法調用XXAsync它,這個時候,真正的運行在另外“任務上”了哦,會運行在其它線程!

寫到這裡,我們並沒有和await有關哦。

那麼在XXAsync()方法前加await到底有何不同?

這裡首先要澄清的是:加不加 await 與 XXAsync()是非同步還是同步的並沒有關係!

await 真正的目的只有一個 在調用非同步方法 XXAsync() 時掛起此方法,它認為這個方法是比較耗時的方法,主線程或調用此方法的線程不要在此方法等待。
並同時作個標記,當前執行的線程運行結束時,將由此處恢復運行,所以在await 標記的非同步方法下麵的代碼或方法將不能運行,必須等待這個方法完成!


如:
private static async Task XAsync()
  {
           
     await  XXAsync();

     OtherMothod(); 

  }

 

在運行至 await XXAsync()時,調用方法XAsync()者將不再運行,直接返回,就像我們程式代碼中的return語句。這樣做的好處是,調用線程,不將等待此耗時線程。直接讓調用線程往下運行,

如果調用線程向上一直存在await 鏈,就像方法套方法里有return一樣,會逐層向上返回,一直返回到主線程。

而每個“子線程”去等待耗時的I/O處理,比如 操作資料庫和網路流任何,這裡特別強調I/O處理,試想下,我們的程式要查詢一個資料庫,可能要有點時間,此時查詢命令可能已運行在了sql資料庫里,

如果資料庫在遠程另外一臺機器上呢?我們的"子線程或者任務“只是等待,而此時的主線程可能已完成。

如何理解主線程已完成呢?Asp.net Mvc 的機制就在這裡,我們都知道,IIS里的線程池是有限的,每次的Client端請求,都會到線程池裡取一個空閑的線程,如果主線程一直在”占用“線程池,

很快線程池就會被利用完啦。此時我們平時說的”吞吐量“的高低就是與此息息相關!當線程池被請求完後,再次有新的Client端請求,要會等待線程池的釋放。

而mvc 就引用了控制器里非同步方法的機制,原理就是讓耗時的線程,直接返回,交給主線程,從而主線程會第一時間釋放線程池的占用,而耗時的子線程完成時,將會在await標記從繼續運行,

由此可以看出Mvc的非同步將大大提高了應用程式的”吞吐量“。

至於具體的mvc非同步編程機制與原理,網上一大把,也可以看看mvc的源代碼,這裡只簡單的說下,本文的主旨await標記給非同步帶來的作用。



話題轉回來:

那麼我們何時在調用非同步方法用await 作”標記“呢?

看看microsoft的經典例子

// Three things to note in the signature:  
//  - The method has an async modifier.   
//  - The return type is Task or Task<T>. (See "Return Types" section.)  
//    Here, it is Task<int> because the return statement returns an integer.  
//  - The method name ends in "Async."  
async Task<int> AccessTheWebAsync()  
{   
    // You need to add a reference to System.Net.Http to declare client.  
    HttpClient client = new HttpClient();  

    // GetStringAsync returns a Task<string>. That means that when you await the  
    // task you'll get a string (urlContents).  
    Task<string> getStringTask = client.GetStringAsync("http://msdn.microsoft.com");  1)

    // You can do work here that doesn't rely on the string from GetStringAsync.  
    DoIndependentWork();  2)

    // The await operator suspends AccessTheWebAsync.  
    //  - AccessTheWebAsync can't continue until getStringTask is complete.  
    //  - Meanwhile, control returns to the caller of AccessTheWebAsync.  
    //  - Control resumes here when getStringTask is complete.   
    //  - The await operator then retrieves the string result from getStringTask.  
    string urlContents = await getStringTask;  3)

    // The return statement specifies an integer result.  
    // Any methods that are awaiting AccessTheWebAsync retrieve the length value.  
    return urlContents.Length;  
}  

對上面的例子做了1)、2)、3)紅色標記,

在1)處定義了一個網路流操作,認為此方法可能會耗時,如果在此處,添加一個await client.GetStringAsync("http://msdn.microsoft.com")

對程式來說,這是一個非同步操作,開闢了一個新線程,同時程式在此處返回給被調線程或UI,等網路流返回結束時繼續在運換醒被調線程或主線程,並由於繼續往下運行其它方法。
對於像這樣的網站,一級一級的向上await 不會造成任何的吞吐量或響應速度的降低,可是新的問題來了,接下來2)處
DoIndependentWork()方法必須等到1)完成才能繼續運行,“跳出來站在更高點看下”,這不相當於”同步“了嗎?按順序一步一步來的,子線程關沒有給我們太多的優勢。

是的,確實如此。

我們知道,讓“子線程或任務”幹事情,主線程繼續他的活兒才對的,所以在1)處,不應該用await,讓”線程或任務再跑一會“。
由於我們沒有加await,於是主線程或調用線程與調用網路流的子線程”一塊運行“了。

當程式運行至3)時,1)標記處的任務可能已經完成或者快要完成,此時用了await目的只有一個,下麵的一句話 urlContents.Length 要用到非同步結果,必須待等結束,並同時向調用線程或主線程返回標記,

以使調用者最快的響應,而不是等待以至於阻塞。

回過頭來看下:我們即要多線程或多任務執行我們的程式,讓耗時的任務得到執行,同時又要給調用者快速響應,不管他有沒有完成任務! 這才是真正的目的。

再想想我們前面說的,DoIndependentWork()調用,加不加await,方法肯定是執行的,同時與該方法非同步還是同步也沒有關係,只是要不要做”標記“而已

至於加不加標記,就是上面我們解釋的理由,忘了,回過頭來看看吧

再來看看下麵的問題:

如果一個方法里存在多個await,會如何執行呢?

我們可以按照上面的猜想下,await某些時候功能很像return,多個await,相必,第一個awiat會返回,並作標記,後面的任何代碼都要等待 如:


private static async TaskXAsync()
  {
           
    await  XXAsync();

    await  XXXAsync(); 
  }

 

事實情況確實如此,XXXAsync()必須等待XXAsync()方法執行結束!此時不會影響調用者的響應速度,但會影響我們代碼的執行效率,這點和兩個共步方法稍有區別 

private static async TaskXAsync()
  {
           
    XX();

    XXX(); 
  }
像上面的例子XX()和XXX()兩同步方法,不僅按順序執行,而且調用者也無法拿回調用權,也就是無法及時響應,必須待兩個方法都結束為止。

”偷偷的想下“,我想在XX(),XXX()方法前加一個await 不就行了嗎?

回過頭來想想,上面說過:"await 僅可用於由 async 關鍵字修改的非同步方法中"

實際上我們在VS加上那麼await會報錯,編譯不過!   希望破滅。。。此時已經看出被async修飾的目的。因為XX()和XXX()並沒被修飾。

那好了,我們就強制在同步方法上用async !

XX()
{
    code here...
}

 

實際上當我們強制在XX()方法上加上Async時VS已經提示如下:

 

很顯然,同步方法,想提高調用者的響應速度是不可能僅僅靠async 就能完成的!根本原因就是調用者與執行方法在同一個線程上。

 

 

 再回過頭來繼續我們上面的例子

 

private static async TaskXAsync()
  {
           
    await  XXAsync();

    await  XXXAsync(); 
  }

上面已清楚,這兩個僅僅按順序執行,並不能並行執行,勢必影響執行效率,那麼如何才能讓他們並行執行呢?
microsoft有專門的方法 Task.WhenAll(Tasks) 我們可以看看microsoft的例子 如:

await SumPageSizesAsync();
 private async Task SumPageSizesAsync()
        {
            // Make a list of web addresses.
            List<string> urlList = SetUpURLList();


            // Declare an HttpClient object and increase the buffer size. The
            // default buffer size is 65,536.
            HttpClient client = new HttpClient() { MaxResponseContentBufferSize = 1000000 };

            // Create a query.
            IEnumerable<Task<int>> downloadTasksQuery = 
                from url in urlList select ProcessURL(url, client);

            // Use ToArray to execute the query and start the download tasks.
            Task<int>[] downloadTasks = downloadTasksQuery.ToArray();

            // You can do other work here before awaiting.

            // Await the completion of all the running tasks.
            int[] lengths = await Task.WhenAll(downloadTasks);

            //// The previous line is equivalent to the following two statements.
            //Task<int[]> whenAllTask = Task.WhenAll(downloadTasks);
            //int[] lengths = await whenAllTask;

            int total = lengths.Sum();

            //var total = 0;
            //foreach (var url in urlList)
            //{
            //    // GetByteArrayAsync returns a Task<T>. At completion, the task
            //    // produces a byte array.
            //    byte[] urlContent = await client.GetByteArrayAsync(url);

            //    // The previous line abbreviates the following two assignment
            //    // statements.
            //    Task<byte[]> getContentTask = client.GetByteArrayAsync(url);
            //    byte[] urlContent = await getContentTask;

            //    DisplayResults(url, urlContent);

            //    // Update the total.
            //    total += urlContent.Length;
            //}

            // Display the total count for all of the web addresses.
            resultsTextBox.Text +=
                string.Format("\r\n\r\nTotal bytes returned:  {0}\r\n", total);
        }

 

// The actions from the foreach loop are moved to this async method.
        async Task<int> ProcessURL(string url, HttpClient client)
        {
            byte[] byteArray = await client.GetByteArrayAsync(url);
            DisplayResults(url, byteArray);
            return byteArray.Length;
        }

 

  private List<string> SetUpURLList()
        {
            List<string> urls = new List<string> 
            { 
                "http://msdn.microsoft.com",
                "http://msdn.microsoft.com/en-us/library/hh290136.aspx",
                "http://msdn.microsoft.com/en-us/library/ee256749.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290138.aspx",
                "http://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "http://msdn.microsoft.com/en-us/library/dd470362.aspx",
                "http://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "http://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "http://msdn.microsoft.com/en-us/library/ff730837.aspx"
            };
            return urls;
        }

 

上面的例子很簡單了,組合任務Tasks,傳給 await Task.WhenAll(Tasks) 這樣,多個await 就並行得到了執行。

 

從上面的例子,我們看得出,每個嵌套的方法里,都是層層向上await,這就是await鏈,不僅要作標記在子線程完成時,在此處”喚醒“同時達到快速響應調用線程,逐層向上返回,結果只有一個,最終讓最外層的調用者及時響應,而不用等待,就像MVC原理一樣,提高“吞吐量”。

但是中間有一個方法,沒向上await,被調用者依然是按照執行的方式決定是同步還是非同步。被調者,要是有返回值的,調用者,是沒辦法獲取到返回值的,因為,我們並沒辦法知道,此方法是否已完成,所以,可能在以後的某段代碼中依然要用await 調用。



小結:await與async並不能決定方法是同步還是非同步,而真正執行非同步的還是靠Task、非同步委托或其它方式,await的主要作用是,
掛機耗時非同步方法,把控制權及時的交給調用者,併在被調用者完成任務時,能夠在此喚醒,並繼續執行其它方法。
本節的內容,部分例子只起到說明作用,來原於實踐的驗證,由於時間倉促,並沒有提供完整的案例。
同時本節內容主要用簡單的語言來加以說明,希望能給讀者闡明原理,如果讀者希望更清楚的知道await和async,可以查看源代碼


如果對於非同步的更多瞭解請參考:

大話非同步與並行(一)

大話非同步與並行(二)

大話非同步與並行(三)

 

本文部分實例參考 

await(C# 參考) https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/await

 

使用 Async 和 Await 的非同步編程 (C#)  https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/index

 

Task.WhenAll 方法 (IEnumerable<Task>)  https://msdn.microsoft.com/zh-cn/library/windows/apps/hh160384(v=vs.110)

 

 

 

作者:谷歌's谷歌's博客園
出處:http://www.cnblogs.com/laogu2/ 歡迎轉載,但任何轉載必須保留完整文章,在顯要地方顯示署名以及原文鏈接。如您有任何疑問或者授權方面的協商,請給我留言

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 今天跟著學習了一篇關於表格的排序、過濾與分頁功能的博客,下邊分享一下學到的知識與心得: 一、應用之前樣式,增加測試數據 對Views —— Account —— Index.cshtml進行如下修改: (1)應用佈局頁 _LayoutAdmin.cshtml @{ ViewBag.Title = " ...
  • .Net常用類庫 一、String成員方法(常用) 1,bool Contains(string str) 判斷字元串對象是否包含給定的內容 2,bool StartsWith(String str):判斷字元串對象是否以給定的字元串開始。 3,bool EndsWith(String str):判 ...
  • 前言作為一名合格的furry,我不僅要吸娜娜奇,還要天天泡在fa吸大觸們的furry作品,這其中難免遇到某個十分喜愛的作者,於是便想down空此作者的所有作品。鑒於一張張的保存實在費時費力,寫個爬蟲來解決眼前的問題似乎再好不過了,所以便有了現在這個下載器。功能介紹根據作者名批量下載此作者的所有作品,... ...
  • 這是今天幫 "檸檬" 分析一個 "AsyncLocal相關的問題" 時發現的. 試想這個代碼輸出的值是多少? 答案是123. 為什麼修改了 的值卻無效呢? 這要從AsyncLocal的運作機制說起. 首先這是 "AsyncLocal的源代碼" : 獲取和設置值用的是 和`ExecutionConte ...
  • WCF系統內置綁定列表 編碼格式 一個綁定,適用於與符合 WS-Basic Profile的Web服務(例如基於 ASP.NET Web 服務(ASMX)的服務)進行的通信。 此綁定使用HTTP作為傳輸協議,並使用文本/XML作為預設的消息編碼 Text, MTOM WCF各系統綁定所支持的功能 ...
  • 在 "上一篇" 我們對CoreCLR中的JIT有了一個基礎的瞭解, 這一篇我們將更詳細分析JIT的實現. JIT的實現代碼主要在 "https://github.com/dotnet/coreclr/tree/master/src/jit" 下, 要對一個的函數的JIT過程進行詳細分析, 最好的辦法 ...
  • ASP.NET Core 2.0 開源Git HTTP Server,實現類似 GitHub、GitLab。 GitHub:https://github.com/linezero/GitServer 設置 需要先安裝Git,並確保git 命令可以執行,GitPath 可以是 git 的絕對路徑。 目 ...
  • 一、關於分部視圖(Partial View) Partial View是可以應用在View中的,編寫一次,在其他View中可以被反覆使用。通常都是放在"Views——Shared"文件夾中。 1. 創建Partial View:右鍵"Views——Shared"文件夾添加分部視圖。 2. 使用Par ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...