溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

c#中異步編程的示例分析

發(fā)布時(shí)間:2021-03-29 10:37:28 來(lái)源:億速云 閱讀:141 作者:小新 欄目:開發(fā)技術(shù)

這篇文章給大家分享的是有關(guān)c#中異步編程的示例分析的內(nèi)容。小編覺得挺實(shí)用的,因此分享給大家做個(gè)參考,一起跟隨小編過(guò)來(lái)看看吧。

一、什么算異步?

??廣義來(lái)講,兩個(gè)工作流能同時(shí)進(jìn)行就算異步,例如,CPU與外設(shè)之間的工作流就是異步的。在面向服務(wù)的系統(tǒng)中,各個(gè)子系統(tǒng)之間通信一般都是異步的,例如,訂單系統(tǒng)與支付系統(tǒng)之間的通信是異步的,又如,在現(xiàn)實(shí)生活中,你去館子吃飯,工作流是這樣的,點(diǎn)菜->下單->做你的事->上菜->吃飯,這個(gè)也是異步的,具體來(lái)講你和廚師之間是異步的,異步是如此重要,因外它代表者高效率(兩者或兩者以上的工作可以同時(shí)進(jìn)行),但復(fù)雜,同步的世界簡(jiǎn)單,但效率極極低。

二、在編程中的異步

??在編程中,除了同步和異步這兩個(gè)名詞,還多了一個(gè)阻塞和非阻塞,其中,阻塞和非阻塞是針對(duì)線程的概念,那么同步和異步是針對(duì)誰(shuí)呢?其實(shí)很多情況下同步和異步并沒有具體針對(duì)某一事物,所以導(dǎo)致了針對(duì)同步阻塞、同步非阻塞、異步阻塞、異步非阻塞這幾個(gè)概念的模糊不清。并且也確實(shí)沒有清晰的邊界,請(qǐng)看以下例子:

  public static void DoWorkA()
  {
    Thread thread = new Thread(() => 
    {
      Console.WriteLine("WorkA Done!");
    });
    thread.Start();
  }

  public static void DoWordB()
  {
    Thread thread = new Thread(() =>
    {
      Console.WriteLine("WorkB Done!");
    });
    thread.Start();
  }
  static void Main(string[] args)
  {
    DoWorkA();
    DoWordB();
  }

??假設(shè)運(yùn)行該代碼的CPU是單核單線程,那么請(qǐng)問(wèn)?DoWorkA()、DoWorkB()這兩個(gè)函數(shù)是異步的嗎?因?yàn)镃PU是單核,所以根本不能同時(shí)運(yùn)行兩個(gè)函數(shù),那么從這個(gè)層次來(lái)講,他們之間其實(shí)是同步的,但是,現(xiàn)實(shí)的情況是我們一般都認(rèn)為他們之間是異步的,因?yàn)槲覀兪菑拇a的執(zhí)行順序角度考慮的,而不是從CPU本身的工作流程考慮的。所以要分上下文考慮。再請(qǐng)看下面這個(gè)例子:

  static void Main(string[] args)
  {
    DoWorkA();
    QueryDataBaseSync();//同步查詢數(shù)據(jù)庫(kù)
    DoWorkB();
  }

??從代碼的執(zhí)行順序角度考慮,這三個(gè)函數(shù)執(zhí)行就是同步的,但是,從CPU的角度來(lái)講,數(shù)據(jù)庫(kù)查詢工作(另一臺(tái)機(jī)器)和CPU計(jì)算工作是異步的,在下文中,沒有做特別申明,則都是從代碼的執(zhí)行順序角度來(lái)討論同步和異步。
??再解釋一下阻塞和非阻塞以及相關(guān)的知識(shí):

??阻塞特指線程由運(yùn)行狀態(tài)轉(zhuǎn)換到掛起狀態(tài),但CPU并不會(huì)阻塞,操作系統(tǒng)會(huì)切換另一個(gè)處于就緒狀態(tài)的線程,并轉(zhuǎn)換成運(yùn)行狀態(tài)。導(dǎo)致線程被阻塞的原因有很多,如:發(fā)生系統(tǒng)調(diào)用(應(yīng)用程序調(diào)用系統(tǒng)API,如果調(diào)用成功,會(huì)發(fā)生從應(yīng)用態(tài)->內(nèi)核態(tài)->應(yīng)用態(tài)的轉(zhuǎn)換開銷),但此時(shí)外部條件并沒有滿足,如從Socket內(nèi)核緩沖區(qū)讀數(shù)據(jù),此時(shí)緩沖區(qū)還沒有數(shù)據(jù),則會(huì)導(dǎo)致操作系統(tǒng)掛起該線程,切換到另一個(gè)處于就緒態(tài)的線程然后給CPU執(zhí)行,這是主動(dòng)調(diào)用導(dǎo)致的,還有被動(dòng)導(dǎo)致的,對(duì)于現(xiàn)在的分時(shí)操作系統(tǒng),在一個(gè)線程時(shí)間片到了之后,會(huì)發(fā)生時(shí)鐘中斷信號(hào),然后由操作系統(tǒng)預(yù)先寫好的中斷函數(shù)處理,再按一定策略(如線程優(yōu)先級(jí))切換至另一個(gè)線程執(zhí)行,導(dǎo)致線程被動(dòng)地從運(yùn)行態(tài)轉(zhuǎn)換成掛起狀態(tài)。
??非阻塞一般指函數(shù)調(diào)用不會(huì)導(dǎo)致執(zhí)行該函數(shù)的線程從運(yùn)行態(tài)轉(zhuǎn)換成掛起狀態(tài)。

三、原始的異步編程模式之回調(diào)函數(shù)#

??在此之前,我們先稍微了解下圖形界面的工作原理,GUI程序大概可以用以下偽代碼表示:

While(GetMessage() != 'exit') //從線程消息隊(duì)列中獲取一個(gè)消息,線程消息隊(duì)列由系統(tǒng)維護(hù),例如鼠標(biāo)移動(dòng)事件,這個(gè)事件由操作系統(tǒng)捕捉,并投遞到線程的消息隊(duì)列中。
{
  msg = TranslateMessage();//轉(zhuǎn)換消息格式
  DispatherMessage(msg);//分發(fā)消息到相應(yīng)的處理函數(shù)
}

??其中DispatherMessage根據(jù)不同的消息類型,調(diào)用不同的消息處理函數(shù),例如鼠標(biāo)移動(dòng)消息(MouseMove),此時(shí)消息處理函數(shù)可以根據(jù)MouseMove消息中的值,做相應(yīng)的處理,例如調(diào)用繪圖相關(guān)函數(shù)畫出鼠標(biāo)此刻的形狀。
??一般來(lái)講,我們稱這個(gè)循環(huán)為消息循環(huán)(事件循環(huán)、EventLoop),編程模型稱為消息驅(qū)動(dòng)模型(事件驅(qū)動(dòng)),在UI程序中,執(zhí)行這部分代碼的線程一般只有一個(gè)線程,稱為UI線程,為什么是單線程,讀者可以去思考。
??以上為背景知識(shí)。現(xiàn)在,我們思考,假如在UI線程中執(zhí)行一個(gè)會(huì)導(dǎo)致UI線程被阻塞的操作,或者在UI線程執(zhí)行一個(gè)純CPU計(jì)算的工作,會(huì)發(fā)生什么樣的結(jié)果?如果執(zhí)行一個(gè)導(dǎo)致UI線程被阻塞的操作,那么這個(gè)消息循環(huán)就會(huì)被迫停止,導(dǎo)致相關(guān)的繪圖消息不能被相應(yīng)的消息處理函數(shù)處理,表現(xiàn)就是UI界面“假死”,直到UI線程被喚起。如果是純CPU計(jì)算的工作,那么也會(huì)導(dǎo)致其他消息不能被及時(shí)處理,也會(huì)導(dǎo)致界面“假死”現(xiàn)象。如何處理這種情況?寫異步代碼。
??我們先用控制臺(tái)程序模擬這個(gè)UI程序,后面以此為基礎(chǔ)。

  public static string GetMessage()
  {
    return Console.ReadLine();
  }

  public static string TranslateMessage(string msg)
  {
    return msg;
  }

  public static void DispatherMessage(string msg)
  {
    switch (msg)
    {
      case "MOUSE_MOVE":
        {
          OnMOUSE_MOVE(msg);
          break;
        }
      default:
        break;
    }
  }

  public static void OnMOUSE_MOVE(string msg)
  {
    Console.WriteLine("開始繪制鼠標(biāo)形狀");
  }


  static void Main(string[] args)
  {
    while(true)
    {
      string msg = GetMessage();
      if (msg == "quit") return;
      string m = TranslateMessage(msg);
      DispatherMessage(m);
    }
  }

1、回調(diào)函數(shù)

??上面那個(gè)例子,一但外部有消息到來(lái),根據(jù)不同的消息類型,調(diào)用不同的處理函數(shù),如鼠標(biāo)移動(dòng)時(shí)產(chǎn)生MOUSE_DOWN消息,相應(yīng)的消息處理函數(shù)就開始重新繪制鼠標(biāo)的形狀,這樣一但你鼠標(biāo)移動(dòng),就你會(huì)發(fā)現(xiàn)屏幕上的鼠標(biāo)跟著移動(dòng)了。
??現(xiàn)在假設(shè)我們?cè)黾右粋€(gè)消息處理函數(shù),如OnMOUSE_DOWN,這個(gè)函數(shù)內(nèi)部進(jìn)行了一個(gè)阻塞的操作,如發(fā)起一個(gè)HTTP請(qǐng)求,在HTTP請(qǐng)求回復(fù)到來(lái)前,該UI程序會(huì)“假死”,我們編寫異步代碼來(lái)解決這個(gè)問(wèn)題。

  public static int Http()
  {
    Thread.Sleep(1000);//模擬網(wǎng)絡(luò)IO延時(shí)
    return 1;
  }
  public static void HttpAsync(Action<int> action,Action error)
  {
    //這里我們用另一個(gè)線程來(lái)實(shí)現(xiàn)異步IO,由于Http方法內(nèi)部是通過(guò)Sleep來(lái)模擬網(wǎng)絡(luò)IO延時(shí)的,這里也只能通過(guò)另一個(gè)線程來(lái)實(shí)現(xiàn)異步IO
    //但記住,多線程是實(shí)現(xiàn)異步IO的一個(gè)手段而已,它不是必須的,后面會(huì)講到如何通過(guò)一個(gè)線程來(lái)實(shí)現(xiàn)異步IO。
    Thread thread = new Thread(() => 
    {
      try
      {
        int res = Http();
        action(res);
      }
      catch
      {
        error();
      }
  
    });

    thread.Start();
  }
  public static void OnMouse_DOWN(string msg)
  {
    HttpAsync(res => 
    {
      Console.WriteLine("請(qǐng)求成功!");
      //使用該結(jié)果做一些工作
    }, () => 
    {
      Console.WriteLine("請(qǐng)求發(fā)生錯(cuò)誤!");
    });
  }

??此時(shí)界面不再“假死”了,我們看下代碼可讀性,感覺還行,但是,如果再在回調(diào)函數(shù)里面再發(fā)起類似的異步請(qǐng)求呢?(有人可能有疑問(wèn),為什么還需要發(fā)起異步請(qǐng)求,我發(fā)同步請(qǐng)求不行嗎?這都是在另一個(gè)線程里了。是的,在這個(gè)例子里是沒問(wèn)題的,但真實(shí)情況是,執(zhí)行回調(diào)函數(shù)的代碼,一般都會(huì)在UI線程,因?yàn)槿〉媒Y(jié)果后需要更新相關(guān)UI組件上的界面,例如文字,而更新界面的操作都是放在UI線程里的,如何把回調(diào)函數(shù)放到UI線程上執(zhí)行,這里不做討論,在.NET中,這跟同步上下文(Synchronization context)有關(guān),后面會(huì)講到),那么代碼會(huì)變成這樣

  public static void OnMouse_DOWN(string msg)
  {
    HttpAsync(res => 
    {
      Console.WriteLine("請(qǐng)求成功!");
      //使用該結(jié)果做一些工作

      HttpAsync(r1 => 
      {
        //使用該結(jié)果做一些工作

        HttpAsync(r2 => 
        {
          //使用該結(jié)果做一些工作
        }, () => 
        {

        });
      }, () => 
      {

      });
    }, () => 
    {
      Console.WriteLine("請(qǐng)求發(fā)生錯(cuò)誤!");
    });
  }

??寫過(guò)JS的同學(xué)可能很清楚,這叫做“回調(diào)地獄”,如何解決這個(gè)問(wèn)題?JS中有Promise,而C#中有Task,我們先用Task來(lái)寫這一段代碼,然后自己實(shí)現(xiàn)一個(gè)與Task功能差不多的簡(jiǎn)單的類庫(kù)。

  public static Task<int> HttpAsync()
  {
    return Task.Run(() => 
    {
      return Http();
    });
  }


  public static void OnMouse_DOWN(string msg)
  {
    HttpAsync()
      .ContinueWith(t => 
      {
        if(t.Status == TaskStatus.Faulted)
        {

        }else if(t.Status == TaskStatus.RanToCompletion)
        {
          //做一些工作
        }
      })
      .ContinueWith(t => 
      {
        if (t.Status == TaskStatus.Faulted)
        {

        }
        else if (t.Status == TaskStatus.RanToCompletion)
        {
          //做一些工作
        }
      })
      .ContinueWith(t => 
      {
        if (t.Status == TaskStatus.Faulted)
        {

        }
        else if (t.Status == TaskStatus.RanToCompletion)
        {
          //做一些工作
        }
      });
  }

??是不是感覺清爽了許多?這是編寫異步代碼的第一個(gè)躍進(jìn)。下篇將會(huì)介紹,如何自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的Task。后面還會(huì)提到C#中async/await的本質(zhì)作用,async/await是怎么跟Task聯(lián)系起來(lái)的,怎么把自己寫的Task庫(kù)與async/await連結(jié)起來(lái),以及一個(gè)線程如何實(shí)現(xiàn)異步IO。

感謝各位的閱讀!關(guān)于“c#中異步編程的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,讓大家可以學(xué)到更多知識(shí),如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到吧!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI