欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺談Async和Await如何簡化異步編程(幾個實例讓你徹底明白)

 更新時間:2016年12月27日 10:06:14   作者:MarkZeuckerberg  
本篇文章主要介紹了淺談Async和Await如何簡化異步編程,小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

引言

C#5.0中async和await兩個關鍵字,這兩個關鍵字簡化了異步編程,之所以簡化了,還是因為編譯器給我們做了更多的工作,下面就具體看看編譯器到底在背后幫我們做了哪些復雜的工作的。

同步代碼存在的問題

對于同步的代碼,大家肯定都不陌生,因為我們平常寫的代碼大部分都是同步的,然而同步代碼卻存在一個很嚴重的問題,例如我們向一個Web服務器發(fā)出一個請求時,如果我們發(fā)出請求的代碼是同步實現的話,這時候我們的應用程序就會處于等待狀態(tài),直到收回一個響應信息為止,然而在這個等待的狀態(tài),對于用戶不能操作任何的UI界面以及也沒有任何的消息,如果我們試圖去操作界面時,此時我們就會看到”應用程序為響應”的信息(在應用程序的窗口旁),相信大家在平常使用桌面軟件或者訪問web的時候,肯定都遇到過這樣類似的情況的,對于這個,大家肯定會覺得看上去非常不舒服。引起這個原因正是因為代碼的實現是同步實現的,所以在沒有得到一個響應消息之前,界面就成了一個”卡死”狀態(tài)了,所以這對于用戶來說肯定是不可接受的,因為如果我要從服務器上下載一個很大的文件時,此時我們甚至不能對窗體進行關閉的操作的。為了具體說明同步代碼存在的問題(造成界面開始),下面通過一個程序讓大家更形象地看下問題所在:

// 單擊事件
    private void btnClick_Click(object sender, EventArgs e)
    {
      this.btnClick.Enabled = false;

      long length = AccessWeb();
      this.btnClick.Enabled = true;
      // 這里可以做一些不依賴回復的操作
      OtherWork();

      this.richTextBox1.Text += String.Format("\n 回復的字節(jié)長度為: {0}.\r\n", length);
      txbMainThreadID.Text = Thread.CurrentThread.ManagedThreadId.ToString();
    }

    private long AccessWeb()
    {
      MemoryStream content = new MemoryStream();

      // 對MSDN發(fā)起一個Web請求
      HttpWebRequest webRequest = WebRequest.Create("http://msdn.microsoft.com/zh-cn/") as HttpWebRequest;
      if (webRequest != null)
      {
        // 返回回復結果
        using (WebResponse response = webRequest.GetResponse())
        {
          using (Stream responseStream = response.GetResponseStream())
          {
            responseStream.CopyTo(content);
          }
        }
      }

      txbAsynMethodID.Text = Thread.CurrentThread.ManagedThreadId.ToString();
      return content.Length;
    }

運行程序后,當我們點擊窗體的 “點擊我”按鈕之后,在得到服務器響應之前,我們不能對窗體進行任何的操作,包括移動窗體,關閉窗體等,具體運行結果如下:

傳統(tǒng)的異步編程來改善程序的響應

上面部分我們已經看到同步方法所帶來的實際問題了,為了解決類似的問題,.NET Framework很早就提供了對異步編程的支持,下面就用.NET 1.0中提出的異步編程模型(APM)來解決上面的問題,具體代碼如下(注釋的部分通過獲得GUI線程的同步上文對象,然后同步調用同步上下文對象的post方法把要調用的方法交給GUI線程去處理,因為控件本來就是由GUI線程創(chuàng)建的,然后由它自己執(zhí)行訪問控件的操作就不存在跨線程的問題了,程序中使用的是調用RichTextBox控件的Invoke方式來異步回調訪問控件的方法,其實背后的原來和注釋部分是一樣的,調用RichTextBox控件的Invoke方法可以獲得創(chuàng)建RichTextBox控件的線程信息(也就是前一種方式的同步上下文),然后讓Invoke回調的方法在該線程上運行):

private void btnClick_Click(object sender, EventArgs e)
    {
      this.richTextBox1.Clear();
      btnClick.Enabled = false;
      AsyncMethodCaller caller = new AsyncMethodCaller(TestMethod);
      IAsyncResult result = caller.BeginInvoke(GetResult, null);

      //// 捕捉調用線程的同步上下文派生對象
      //sc= SynchronizationContext.Current;
    }

    # region 使用APM實現異步編程
    // 同步方法
    private string TestMethod()
    {    
      // 模擬做一些耗時的操作
      // 實際項目中可能是讀取一個大文件或者從遠程服務器中獲取數據等。
      for (int i = 0; i < 10; i++)
      {
        Thread.Sleep(200);
      }

      return "點擊我按鈕事件完成";
    }

    // 回調方法
    private void GetResult(IAsyncResult result)
    {
      AsyncMethodCaller caller = (AsyncMethodCaller)((AsyncResult)result).AsyncDelegate;
      // 調用EndInvoke去等待異步調用完成并且獲得返回值
      // 如果異步調用尚未完成,則 EndInvoke 會一直阻止調用線程,直到異步調用完成
      string resultvalue = caller.EndInvoke(result);
      //sc.Post(ShowState,resultvalue);
      richTextBox1.Invoke(showStateCallback, resultvalue);
    }

    // 顯示結果到richTextBox
    private void ShowState(object result)
    {
      richTextBox1.Text = result.ToString();
      btnClick.Enabled = true;
    }

    // 顯示結果到richTextBox
    //private void ShowState(string result)
    //{
    //  richTextBox1.Text = result;
    //  btnClick.Enabled = true;
    //}
    #endregion

運行的結果為:

C# 5.0 提供的async和await使異步編程更簡單

上面部分演示了使用傳統(tǒng)的異步編程模型(APM)來解決同步代碼所存在的問題,然而在.NET 2.0,.NET 4.0和.NET 4.5中,微軟都有推出新的方式來解決同步代碼的問題,他們分別為基于事件的異步模式,基于任務的異步模式和提供async和await關鍵字來對異步編程支持。關于前兩種異步編程模式,在我前面的文章中都有介紹,大家可以查看相關文章進行詳細地了解,本部分就C# 5.0中的async和await這兩個關鍵字如何實現異步編程的問題來給大家介紹下。下面通過代碼來了解下如何使用async和await關鍵字來實現異步編程,并且大家也可以參看前面的博客來對比理解使用async和await是異步編程更簡單。

private async void btnClick_Click(object sender, EventArgs e)
    {
      long length = await AccessWebAsync();

      // 這里可以做一些不依賴回復的操作
      OtherWork();

      this.richTextBox1.Text += String.Format("\n 回復的字節(jié)長度為: {0}.\r\n", length);
      txbMainThreadID.Text = Thread.CurrentThread.ManagedThreadId.ToString();
    }

    // 使用C# 5.0中提供的async 和await關鍵字來定義異步方法
    // 從代碼中可以看出C#5.0 中定義異步方法就像定義同步方法一樣簡單。
    // 使用async 和await定義異步方法不會創(chuàng)建新線程,
    // 它運行在現有線程上執(zhí)行多個任務.
    // 此時不知道大家有沒有一個疑問的?在現有線程上(即UI線程上)運行一個耗時的操作時,
    // 為什么不會堵塞UI線程的呢?
    // 這個問題的答案就是 當編譯器看到await關鍵字時,線程會
    private async Task<long> AccessWebAsync()
    {
      MemoryStream content = new MemoryStream();

      // 對MSDN發(fā)起一個Web請求
      HttpWebRequest webRequest = WebRequest.Create("http://msdn.microsoft.com/zh-cn/") as HttpWebRequest;
      if (webRequest != null)
      {
        // 返回回復結果
        using (WebResponse response = await webRequest.GetResponseAsync())
        {
          using (Stream responseStream = response.GetResponseStream())
          {
            await responseStream.CopyToAsync(content);
          }
        }
      }

      txbAsynMethodID.Text = Thread.CurrentThread.ManagedThreadId.ToString() ;
      return content.Length;
    }

    private void OtherWork()
    {
      this.richTextBox1.Text += "\r\n等待服務器回復中.................\n";
    }

運行結果如下:

async和await關鍵字剖析

我們對比下上面使用async和await關鍵字來實現異步編程的代碼和在第二部分的同步代碼,有沒有發(fā)現使用async和await關鍵字的異步實現和同步代碼的實現很像,只是異步實現中多了async和await關鍵字和調用的方法都多了async后綴而已。正是因為他們的實現很像,所以我在第四部分才命名為使用async和await使異步編程更簡單,就像我們在寫同步代碼一樣,并且代碼的coding思路也是和同步代碼一樣,這樣就避免考慮在APM中委托的回調等復雜的問題,以及在EAP中考慮各種事件的定義。

從代碼部分我們可以看出async和await的使用確實很簡單,我們就如在寫同步代碼一般,但是我很想知道編譯器到底給我們做了怎樣的處理的?并且從運行結果可以發(fā)現,運行異步方法的線程和GUI線程的ID是一樣的,也就是說異步方法的運行在GUI線程上,所以就不用像APM中那樣考慮跨線程訪問的問題了(因為通過委托的BeginInvoke方法來進行回調方法時,回調方法是在線程池線程上執(zhí)行的)。下面就用反射工具看看編譯器把我們的源碼編譯成什么樣子的:

// 編譯器為按鈕Click事件生成的代碼
private void btnClick_Click(object sender, EventArgs e)
{
  <btnClick_Click>d__0 d__;
  d__.<>4__this = this;
  d__.sender = sender;
  d__.e = e;
  d__.<>t__builder = AsyncVoidMethodBuilder.Create();
  d__.<>1__state = -1;
  d__.<>t__builder.Start<<btnClick_Click>d__0>(ref d__);
}

看到上面的代碼,作為程序員的我想說——編譯器你怎么可以這樣呢?怎么可以任意篡改我的代碼呢?這樣不是侵犯我的版權了嗎?你要改最起碼應該告訴我一聲吧,如果我的源碼看到它在編譯器中的實現是上面那樣的,我相信我的源碼會說——難道我中了世間上最惡毒的面目全非腳嗎? 好吧,為了讓大家更好地理清編譯器背后到底做了什么事情,下面就順著上面的代碼摸瓜,我也來展示耍一套還我漂漂拳來幫助大家找到編譯器代碼和源碼的對應關系。我的分析思路為:

1、提出問題——我的click事件的源碼到哪里去了呢?

從編譯器代碼我們可以看到,前面的7句代碼都是對某個類進行賦值的操作,最真正起作用的就是最后Start方法的調用。這里又產生了幾個疑問——d__0是什么類型? 該類型中的<>t__builder字段類型的Start方法到底是做什么用的? 有了這兩個疑問,我們就點擊d__0(反射工具可以讓我們直接點擊查看)來看看它是什么類型   

// <btnClick_Click>d__0類型的定義,從下面代碼可以看出它是一個結構體
// 該類型是編譯器生成的一個嵌入類型
// 看到該類型的實現有沒有讓你聯想到什么?
private struct <btnClick_Click>d__0 : IAsyncStateMachine
{
  // Fields
  public int <>1__state;
  public Form1 <>4__this;
  public AsyncVoidMethodBuilder <>t__builder;
  private object <>t__stack;
  private TaskAwaiter<long> <>u__$awaiter2;
  public long <length>5__1;
  public EventArgs e;
  public object sender;

  // Methods
  private void MoveNext()
  {
    try
    {
      TaskAwaiter<long> CS$0$0001;
      bool <>t__doFinallyBodies = true;
      switch (this.<>1__state)
      {
        case -3:
          goto Label_010E;

        case 0:
          break;

        default:
            // 獲取用于等待Task(任務)的等待者。你要知道某個任務是否完成,我們就需要一個等待者對象對該任務進行一個監(jiān)控,所以微軟就定義了一個等待者對象的
            // 從這里可以看出,其實async和await關鍵字背后的實現原理是基于任務的異步編程模式(TAP)
          // 這里代碼是在線程池線程上運行的
          CS$0$0001 = this.<>4__this.AccessWebAsync().GetAwaiter();
            // 如果任務完成就調轉到Label_007A部分的代碼
          if (CS$0$0001.IsCompleted)
          {
            goto Label_007A;
          }
           
          // 設置狀態(tài)為0為了退出回調方法。
          this.<>1__state = 0;
          this.<>u__$awaiter2 = CS$0$0001;
            // 這個代碼是做什么用的呢?讓我們帶著問題看下面的分析
this.<>t__builder.AwaitUnsafeOnCompleted<TaskAwaiter<long>, Form1.<btnClick_Click>d__0>(ref CS$0$0001, ref this);
          <>t__doFinallyBodies = false;
            // 返回到調用線程,即GUI線程,這也是該方法不會堵塞GUI線程的原因,不管任務是否完成都返回到GUI線程
          return;
      }
      // 當任務完成時,不會執(zhí)行下面的代碼,會直接執(zhí)行Label_007A中代碼
      CS$0$0001 = this.<>u__$awaiter2;
      this.<>u__$awaiter2 = new TaskAwaiter<long>();
      // 為了使再次回調MoveNext代碼
      this.<>1__state = -1;
    Label_007A:
      // 下面代碼是在GUI線程上執(zhí)行的
      CS$0$0001 = new TaskAwaiter<long>();
      long CS$0$0003 = CS$0$0001.GetResult();
      this.<length>5__1 = CS$0$0003;
        // 我們源碼中的代碼這里的
      this.<>4__this.OtherWork();
      this.<>4__this.richTextBox1.Text = this.<>4__this.richTextBox1.Text + string.Format("\n 回復的字節(jié)長度為: {0}.\r\n", this.<length>5__1);
      this.<>4__this.txbMainThreadID.Text = Thread.CurrentThread.ManagedThreadId.ToString();
    }
    catch (Exception <>t__ex)
    {
      this.<>1__state = -2;
      this.<>t__builder.SetException(<>t__ex);
      return;
    }
  Label_010E:
    this.<>1__state = -2;
    this.<>t__builder.SetResult();
  }

  [DebuggerHidden]
  private void SetStateMachine(IAsyncStateMachine param0)
  {
    this.<>t__builder.SetStateMachine(param0);
  }
}

如果你看過我的迭代器專題的話,相信你肯定可以聯想到該結構體就是一個迭代器的一個實現,其主要方法就是MoveNext方法。

從上面的代碼的注釋應該可以幫助我們解決在第一步提到的第一個問題,即<btnClick_Click>d__0是什么類型,下面就分析下第二個問題,從<btnClick_Click>d__0結構體的代碼中可以發(fā)現<>t__builder的類型是AsyncVoidMethodBuilder類型,下面就看看它的Start方法的解釋——運行關聯狀態(tài)機的生成器,即調用該方法就可以開始運行狀態(tài)機,運行狀態(tài)機指的就是執(zhí)行MoveNext方法(MoveNext方法中有我們源碼中所有代碼,這樣就把編譯器生成的Click方法與我們的源碼關聯起來了)。

從上面代碼注釋中可以發(fā)現,當該MoveNext被調用時會立即還回到GUI線程中,同時也有這樣的疑問——剛開始調用MoveNext方法時,任務肯定是還沒有被完成的,但是我們輸出我們源碼中的代碼,必須等待任務完成(因為任務完成才能調轉到Label_007A中的代碼),此時我們應該需要回調MoveNext方法來檢查任務是否完成,(就如迭代器中的,我們需要使用foreach語句一直調用MoveNext方法),然而我們在代碼卻沒有找到回調的任何代碼???

對于這個疑問,回調MoveNext方法肯定是存在的,只是首次看上面代碼的朋友還沒有找到類似的語句而已,上面代碼注釋中我提到了一個問題——這個代碼是做什么用的呢?讓我們帶著問題看下面的分析,其實注釋下面的代碼就是起到回調MoveNext方法的作用,AsyncVoidMethodBuilder.AwaitUnsafeOnCompleted<TAwaiter, TStateMachine> 方法就是調度狀態(tài)機去執(zhí)行MoveNext方法,從而也就解決了回調MoveNext的疑問了。

相信大家從上面的解釋中可以找到源碼與編譯器代碼之間的對應關系了吧, 但是我在分析完上面的之后,又有一個疑問——當任務完成時,是如何退出MoveNext方法的呢?總不能讓其一直回調吧,從上面的代碼的注釋可以看出,當任務執(zhí)行完成之后,會把<>1__state設置為0,當下次再回調MoveNext方法時就會直接退出方法,然而任務沒完成之前,同樣也會把<>1__state設置為0,但是Switch部分后面的代碼又把<>1__state設置為-1,這樣就保證了在任務沒完成之前,MoveNext方法可以被重復回調,當任務完成之后,<>1__state設置為-1的代碼將不會執(zhí)行,而是調轉到Label_007A部分。

經過上面的分析之后,相信大家也可以耍出一套還我漂漂拳去分析異步方法AccessWebAsync(),其分析思路是和btnClick_Click的分析思路是一樣的.這里就不重復啰嗦了。

分析完之后,下面再分享下幾個關于async和await常問的問題

問題一:是不是寫了async關鍵字的方法就代表該方法是異步方法,不會堵塞線程呢?

答: 不是的,對于只標識async關鍵字的(指在方法內沒有出現await關鍵字)的方法,調用線程會把該方法當成同步方法一樣執(zhí)行,所以然而會堵塞GUI線程,只有當async和await關鍵字同時出現,該方法才被轉換為異步方法處理。  

問題二:“async”關鍵字會導致調用方法用線程池線程運行嗎?

答: 不會,被async關鍵字標識的方法不會影響方法是同步還是異步運行并完成,而是,它使方法可被分割成多個片段,其中一些片段可能異步運行,這樣這個方法可能異步完成。這些片段界限就出現在方法內部顯示使用”await”關鍵字的位置處。所以,如果在標記了”async”的方法中沒有顯示使用”await”,那么該方法只有一個片段,并且將以同步方式運行并完成。在await關鍵字出現的前面部分代碼和后面部分代碼都是同步執(zhí)行的(即在調用線程上執(zhí)行的,也就是GUI線程,所以不存在跨線程訪問控件的問題),await關鍵處的代碼片段是在線程池線程上執(zhí)行??偨Y為——使用async和await關鍵字實現的異步方法,此時的異步方法被分成了多個代碼片段去執(zhí)行的,而不是像之前的異步編程模型(APM)和EAP那樣,使用線程池線程去執(zhí)行一整個方法。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。  

相關文章

  • WPF自定義控件和樣式之自定義按鈕(Button)

    WPF自定義控件和樣式之自定義按鈕(Button)

    接觸WPF也有兩個多月了,有了一定的理論基礎和項目經驗,現在打算寫一個系列,做出來一個WPF的控件庫。下面這篇文章主要給大家介紹了關于WPF自定義控件和樣式之自定義按鈕(Button)的相關資料,需要的朋友可以參考下。
    2018-04-04
  • 詳解C#多線程之線程同步

    詳解C#多線程之線程同步

    本文主要介紹了C#線程同步的相關知識。具有很好的參考價值,下面跟著小編一起來看下吧
    2017-01-01
  • C#實現奇偶排序的示例代碼

    C#實現奇偶排序的示例代碼

    奇偶排序是一種簡單的排序算法,該算法通過比較并交換相鄰的元素來完成排序,本文主要介紹了C#實現奇偶排序的示例代碼,具有一定的參考價值,感興趣的可以了解一下
    2023-11-11
  • c#實現輸出本月的月歷

    c#實現輸出本月的月歷

    本篇文章是對使用c#輸出本月月歷的實現的代碼進行了詳細的分析介紹,需要的朋友參考下
    2013-06-06
  • Unity ScrollView實現自動吸附效果

    Unity ScrollView實現自動吸附效果

    這篇文章主要為大家詳細介紹了Unity ScrollView實現自動吸附效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • 使用Unity3D實現選中物體消融特效的方法詳解

    使用Unity3D實現選中物體消融特效的方法詳解

    消融特效中基Shader?Graph實現了消融特效,本文將基于?Shader?實現消融特效,當前實現消融特效的方法主要有?Alpha?測試消融、clip(或?discard)消融,它們的本質都是隨機丟棄一些片元,以實現消融效果,文中有詳細代碼示例,需要的朋友可以參考下
    2023-10-10
  • C#基于簡單工廠模式實現的計算器功能示例

    C#基于簡單工廠模式實現的計算器功能示例

    這篇文章主要介紹了C#基于簡單工廠模式實現的計算器功能,結合簡單實例形式分析了C#使用工廠模式的數值運算相關操作技巧,需要的朋友可以參考下
    2017-11-11
  • C#中面向對象編程機制之多態(tài)學習筆記

    C#中面向對象編程機制之多態(tài)學習筆記

    這篇文章主要介紹了C#中面向對象編程機制之多態(tài)學習筆記,本文總結了個人對多態(tài)的理解以及接口多態(tài)、繼承多態(tài)等內容,并給出了代碼示例,需要的朋友可以參考下
    2015-01-01
  • C#編程中枚舉類型的使用教程

    C#編程中枚舉類型的使用教程

    這篇文章主要介紹了C#編程中枚舉類型的使用,是C#入門學習中的基礎知識,需要的朋友可以參考下
    2016-01-01
  • C#實現漂亮的數字時鐘效果

    C#實現漂亮的數字時鐘效果

    這篇文章主要介紹了C#實現漂亮的數字時鐘效果,涉及時間函數的應用及繪圖的方法,需要的朋友可以參考下
    2014-10-10

最新評論