<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      異步不會提升單次請求結果的時間,但是可以提高Web程序的吞吐量。

      轉自https://blog.csdn.net/chengriyue/article/details/86506656

      新異步的使用

      只能說新異步的使用太簡單(如果僅僅只是說使用)

      方法加上async修飾符,然后使用await關鍵字執行異步方法,即可。對就是如此簡單。像使用同步方法邏輯一樣使用異步。

       
      public async Task<int> Test()
       
      {
       
      var num1 = await GetNumber(1);
       
      var num2 = await GetNumber(num1);
       
      var task = GetNumber(num2);
       
      //或者
       
      var num3 = await task;
       
      return num1 + num2 + num3;
       
      }

       

      新異步的優勢

      在此之前已經有了多種異步模式,為什么還要引入和學習新的async\await異步呢?當然它肯定是有其獨特的優勢。

      我們分兩個方面來分析:WinForm、WPF等單線程UI程序和Web后臺服務程序。

      對于WinForm、WPF等單線程UI程序

      代碼1(舊異步)

      private void button1_Click(object sender, EventArgs e)
       
      {
       
      var request = WebRequest.Create("https://github.com/");
       
      request.BeginGetResponse(new AsyncCallback(t =>
       
      {
       
      //(1)處理請求結果的邏輯必須寫這里
       
      label1.Invoke((Action)(() => { label1.Text = "[舊異步]執行完畢!"; }));//(2)這里跨線程訪問UI需要做處理
       
      }), null);
       
      }
      View Code

       

      代碼2(同步)

      private void button3_Click(object sender, EventArgs e)
       
      {
       
      HttpClient http = new HttpClient();
       
      var htmlStr = http.GetStringAsync("https://github.com/").Result;
       
      //(1)處理請求結果的邏輯可以寫這里
       
      label1.Text = "[同步]執行完畢!";//(2)不在需要做跨線程UI處理了
       
      }
      View Code

       

      代碼3(新異步)

      private async void button2_Click(object sender, EventArgs e)
       
      {
       
      HttpClient http = new HttpClient();
       
      var htmlStr = await http.GetStringAsync("https://github.com/");
       
      //(1)處理請求結果的邏輯可以寫這里
       
      label1.Text = "[新異步]執行完畢!";//(2)不在需要做跨線程UI處理了
       
      }
      View Code

       

      新異步的優勢:

      • 沒有了煩人的回調處理
      • 不會像同步代碼一樣阻塞UI界面(造成假死)
      • 不在像舊異步處理后訪問UI不在需要做跨線程處理
      • 像使用同步代碼一樣使用異步(超清晰的邏輯)

       是的,說得再多還不如看看實際效果圖來得實際:(新舊異步UI線程沒有阻塞,同步阻塞了UI線程)

      【思考】:舊的異步模式是開啟了一個新的線程去執行,不會阻塞UI線程。這點很好理解。可是,新的異步看上去和同步區別不大,為什么也不會阻塞界面呢?

      【原因】:新異步,在執行await表達式前都是使用UI線程,await表達式后會啟用新的線程去執行異步,直到異步執行完成并返回結果,然后再回到UI線程(據說使用了SynchronizationContext)。所以,await是沒有阻塞UI線程的,也就不會造成界面的假死。

      【注意】:我們在演示同步代碼的時候使用了Result。然,在UI單線程程序中使用Result來使異步代碼當同步代碼使用是一件很危險的事(起碼對于不太了解新異步的同學來說是這樣)。至于具體原因稍候再分析(哎呀,別跑啊)。

      對于Web后臺服務程序

      也許對于后臺程序的影響沒有單線程程序那么直觀,但其價值也是非常大的。且很多人對新異步存在誤解。

      【誤解】:新異步可以提升Web程序的性能。

      【正解】:異步不會提升單次請求結果的時間,但是可以提高Web程序的吞吐量。

      1、為什么不會提升單次請求結果的時間?

      其實我們從上面示例代碼(雖然是UI程序的代碼)也可以看出。

       

      2、為什么可以提高Web程序的吞吐量?

      那什么是吞吐量呢,也就是本來只能十個人同時訪問的網站現在可以二十個人同時訪問了。也就是常說的并發量。

      還是用上面的代碼來解釋。[代碼2] 阻塞了UI線程等待請求結果,所以UI線程被占用,而[代碼3]使用了新的線程請求,所以UI線程沒有被占用,而可以繼續響應UI界面。

      那問題來了,我們的Web程序天生就是多線程的,且web線程都是跑的線程池線程(使用線程池線程是為了避免不斷創建、銷毀線程所造成的資源成本浪費),而線程池線程可使用線程數量是一定的,盡管可以設置,但它還是會在一定范圍內。如此一來,我們web線程是珍貴的(物以稀為貴),不能濫用。用完了,那么其他用戶請求的時候就無法處理直接503了。

      那什么算是濫用呢?比如:文件讀取、URL請求、數據庫訪問等IO請求。如果用web線程來做這個耗時的IO操作那么就會阻塞web線程,而web線程阻塞得多了web線程池線程就不夠用了。也就達到了web程序最大訪問數。

      此時我們的新異步橫空出世,解放了那些原本處理IO請求而阻塞的web線程(想偷懶?沒門,干活了。)。通過異步方式使用相對廉價的線程(非web線程池線程)來處理IO操作,這樣web線程池線程就可以解放出來處理更多的請求了。

      不信?下面我們來測試下:

      【測試步驟】:

      1、新建一個web api項目 

      2、新建一個數據訪問類,分別提供同步、異步方法(在方法邏輯執行前后讀取時間、線程id、web線程池線程使用數)

      public class GetDataHelper
       
      {
       
      /// <summary>
       
      /// 同步方法獲取數據
       
      /// </summary>
       
      /// <returns></returns>
       
      public string GetData()
       
      {
       
      var beginInfo = GetBeginThreadInfo();
       
      using (HttpClient http = new HttpClient())
       
      {
       
      http.GetStringAsync("https://github.com/").Wait();//注意:這里是同步阻塞
       
      }
       
      return beginInfo + GetEndThreadInfo();
       
      }
       
       
       
      /// <summary>
       
      /// 異步方法獲取數據
       
      /// </summary>
       
      /// <returns></returns>
       
      public async Task<string> GetDataAsync()
       
      {
       
      var beginInfo = GetBeginThreadInfo();
       
      using (HttpClient http = new HttpClient())
       
      {
       
      await http.GetStringAsync("https://github.com/");//注意:這里是異步等待
       
      }
       
      return beginInfo + GetEndThreadInfo();
       
      }
       
       
       
      public string GetBeginThreadInfo()
       
      {
       
      int t1, t2, t3;
       
      ThreadPool.GetAvailableThreads(out t1, out t3);
       
      ThreadPool.GetMaxThreads(out t2, out t3);
       
      return string.Format("開始:{0:mm:ss,ffff} 線程Id:{1} Web線程數:{2}",
       
      DateTime.Now,
       
      Thread.CurrentThread.ManagedThreadId,
       
      t2 - t1);
       
      }
       
       
       
      public string GetEndThreadInfo()
       
      {
       
      int t1, t2, t3;
       
      ThreadPool.GetAvailableThreads(out t1, out t3);
       
      ThreadPool.GetMaxThreads(out t2, out t3);
       
      return string.Format(" 結束:{0:mm:ss,ffff} 線程Id:{1} Web線程數:{2}",
       
      DateTime.Now,
       
      Thread.CurrentThread.ManagedThreadId,
       
      t2 - t1);
       
      }
       
      }
      View Code

      3、新建一個web api控制器

      [HttpGet]
       
      public async Task<string> Get(string str)
       
      {
       
      GetDataHelper sqlHelper = new GetDataHelper();
       
      switch (str)
       
      {
       
      case "異步處理"://
       
      return await sqlHelper.GetDataAsync();
       
      case "同步處理"://
       
      return sqlHelper.GetData();
       
      }
       
      return "參數不正確";
       
      }
      View Code

      4、發布web api程序,部署到本地iis(同步鏈接:http://localhost:803/api/Home?str=同步處理  異步鏈接:http://localhost:803/api/Home?str=異步處理) 

      5、接著上面的winform程序里面測試請求:(同時發起10個請求)

      private void button6_Click(object sender, EventArgs e)
       
      {
       
      textBox1.Text = "";
       
      label1.Text = "";
       
      Task.Run(() =>
       
      {
       
      TestResultUrl("http://localhost:803/api/Home?str=同步處理");
       
      });
       
      }
       
       
       
      private void button5_Click(object sender, EventArgs e)
       
      {
       
      textBox1.Text = "";
       
      label1.Text = "";
       
      Task.Run(() =>
       
      {
       
      TestResultUrl("http://localhost:803/api/Home?str=異步處理");
       
      });
       
      }
       
       
       
      public void TestResultUrl(string url)
       
      {
       
      int resultEnd = 0;
       
      HttpClient http = new HttpClient();
       
       
       
      int number = 10;
       
      for (int i = 0; i < number; i++)
       
      {
       
      new Thread(async () =>
       
      {
       
      var resultStr = await http.GetStringAsync(url);
       
      label1.Invoke((Action)(() =>
       
      {
       
      textBox1.AppendText(resultStr.Replace(" ", "\r\t") + "\r\n");
       
      if (++resultEnd >= number)
       
      {
       
      label1.Text = "全部執行完畢";
       
      }
       
      }));
       
       
       
      }).Start();
       
      }
       
      }
      View Code

      6、重啟iis,并用瀏覽器訪問一次要請求的鏈接地址(預熱)

      7、啟動winform程序,點擊“訪問同步實現的Web”:

       

      8、重復6,然后重新啟動winform程序點擊“訪問異步實現的Web”

       

       

      看到這些數據有什么感想?

      數據和我們前面的【正解】完全吻合。仔細觀察,每個單次請求用時基本上相差不大。 但是步驟7"同步實現"最高投入web線程數是10,而步驟8“異步實現”最高投入web線程數是3。

      也就是說“異步實現”使用更少的web線程完成了同樣的請求數量,如此一來我們就有更多剩余的web線程去處理更多用戶發起的請求。

      接著我們還發現同步實現請求前后的線程ID是一致的,而異步實現前后線程ID不一定一致。再次證明執行await異步前釋放了主線程。

      【結論】:

      • 使用新異步可以提升Web服務程序的吞吐量
      • 對于客戶端來說,web服務的異步并不會提高客戶端的單次訪問速度。
      • 執行新異步前會釋放web線程,而等待異步執行完成后又回到了web線程上。從而提高web線程的利用率。

      【圖解】:

      Result的死鎖陷阱

      我們在分析UI單線程程序的時候說過,要慎用異步的Result屬性。下面我們來分析:

      private void button4_Click(object sender, EventArgs e)
       
      {
       
      label1.Text = GetUlrString("https://github.com/").Result;
       
      }
       
       
       
      public async Task<string> GetUlrString(string url)
       
      {
       
      using (HttpClient http = new HttpClient())
       
      {
       
      return await http.GetStringAsync(url);
       
      }
       
      }
      View Code

      代碼 GetUlrString("https://github.com/").Result 的Result屬性會阻塞(占用)UI線程,而執行到GetUlrString方法的 await異步的時候又要釋放UI線程。此時矛盾就來了,由于線程資源的搶占導致死鎖。

      且Result屬性和.Wait()方法一樣會阻塞線程。此等問題在Web服務程序里面一樣存在。(區別:UI單次線程程序和web服務程序都會釋放主線程,不同的是Web服務線程不一定會回到原來的主線程,而UI程序一定會回到原來的UI線程)

      我們前面說過,.net為什么會這么智能的自動釋放主線程然后等待異步執行完畢后又回到主線程是因為SynchronizationContext的功勞。

      但這里有個例外,那就是控制臺程序里面是沒有SynchronizationContext的。所以這段代碼放在控制臺里面運行是沒有問題的。

      static void Main(string[] args)
       
      {
       
      Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
       
      GetUlrString("https://github.com/").Wait();
       
      Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
       
      Console.ReadKey();
       
      }
       
       
       
      public async static Task<string> GetUlrString(string url)
       
      {
       
      using (HttpClient http = new HttpClient())
       
      {
       
      Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
       
      return await http.GetStringAsync(url);
       
      }
       
      }
      View Code

      打印出來的都是同一個線程ID

      使用AsyncHelper在同步代碼里面調用異步

      但可是,可但是,我們必須在同步方法里面執行異步怎辦?辦法肯定是有的

      我們首先定義一個AsyncHelper靜態類:

      static class AsyncHelper
       
      {
       
      private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
       
      TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
       
       
       
      public static TResult RunSync<TResult>(Func<Task<TResult>> func)
       
      {
       
      return _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
       
      }
       
       
       
      public static void RunSync(Func<Task> func)
       
      {
       
      _myTaskFactory.StartNew(func).Unwrap().GetAwaiter().GetResult();
       
      }
       
      }
      View Code

      然后調用異步:

      private void button7_Click(object sender, EventArgs e)
      {
          label1.Text = AsyncHelper.RunSync(() => GetUlrString("https://github.com/"));
      }
      View Code

      這樣就不會死鎖了。

      ConfigureAwait

      除了AsyncHelper我們還可以使用Task的ConfigureAwait方法來避免死鎖

      private void button7_Click(object sender, EventArgs e)
       
      {
       
      label1.Text = GetUlrString("https://github.com/").Result;
       
      }
       
       
       
      public async Task<string> GetUlrString(string url)
       
      {
       
      using (HttpClient http = new HttpClient())
       
      {
       
      return await http.GetStringAsync(url).ConfigureAwait(false);
       
      }
       
      }
      View Code

      ConfigureAwait的作用:使當前async方法的await后續操作不需要恢復到主線程(不需要保存線程上下文)。

       

      異常處理

      關于新異步里面拋出異常的正確姿勢。我們先來看下面一段代碼:

      private async void button8_Click(object sender, EventArgs e)
       
      {
       
      Task<string> task = GetUlrStringErr(null);
       
      Thread.Sleep(1000);//一段邏輯。。。。
       
      textBox1.Text = await task;
       
      }
       
       
       
      public async Task<string> GetUlrStringErr(string url)
       
      {
       
      if (string.IsNullOrWhiteSpace(url))
       
      {
       
      throw new Exception("url不能為空");
       
      }
       
      using (HttpClient http = new HttpClient())
       
      {
       
      return await http.GetStringAsync(url);
       
      }
       
      }
      View Code

      調試執行執行流程:

       

       

      在執行完118行的時候竟然沒有把異常拋出來?這不是逆天了嗎。非得在等待await執行的時候才報錯,顯然119行的邏輯執行是沒有什么意義的。讓我們把異常提前拋出:

       

       提取一個方法來做驗證,這樣就能及時的拋出異常了。有朋友會說這樣的太坑爹了吧,一個驗證還非得另外寫個方法。接下來我們提供一個沒有這么坑爹的方式:

       

      在異步函數里面用匿名異步函數進行包裝,同樣可以實現及時驗證。

      感覺也不比前種方式好多少...可是能怎么辦呢。

      異步的實現

      上面簡單分析了新異步能力和屬性。接下來讓我們繼續揭秘異步的本質,神秘的外套下面究竟是怎么實現的。

      首先我們編寫一個用來反編譯的示例:

      class MyAsyncTest
       
      {
       
      public async Task<string> GetUrlStringAsync(HttpClient http, string url, int time)
       
      {
       
      await Task.Delay(time);
       
      return await http.GetStringAsync(url);
       
      }
       
      }
      View Code

       反編譯代碼:點擊看大圖

       

      為了方便閱讀,我們把編譯器自動命名的類型重命名。

       GetUrlStringAsync 方法變成了如此模樣:

      public Task<string> GetUrlStringAsync(HttpClient http, string url, int time)
       
      {
       
      GetUrlStringAsyncdStateMachine stateMachine = new GetUrlStringAsyncdStateMachine()
       
      {
       
      _this = this,
       
      http = http,
       
      url = url,
       
      time = time,
       
      _builder = AsyncTaskMethodBuilder<string>.Create(),
       
      _state = -1
       
      };
       
      stateMachine._builder.Start(ref stateMachine);
       
      return stateMachine._builder.Task;
       
      }
      View Code

      方法簽名完全一致,只是里面的內容變成了一個狀態機 GetUrlStringAsyncdStateMachine  的調用。此狀態機就是編譯器自動創建的。下面來看看神秘的狀態機是什么鬼:

       
      private sealed class GetUrlStringAsyncdStateMachine : IAsyncStateMachine
       
      {
       
      public int _state;
       
      public MyAsyncTest _this;
       
      private string _str1;
       
      public AsyncTaskMethodBuilder<string> _builder;
       
      private TaskAwaiter taskAwaiter1;
       
      private TaskAwaiter<string> taskAwaiter2;
       
       
       
      //異步方法的三個形參都到這里來了
       
      public HttpClient http;
       
      public int time;
       
      public string url;
       
       
       
      private void MoveNext()
       
      {
       
      string str;
       
      int num = this._state;
       
      try
       
      {
       
      TaskAwaiter awaiter;
       
      MyAsyncTest.GetUrlStringAsyncdStateMachine d__;
       
      string str2;
       
      switch (num)
       
      {
       
      case 0:
       
      break;
       
       
       
      case 1:
       
      goto Label_00CD;
       
       
       
      default:
       
      //這里是異步方法 await Task.Delay(time);的具體實現
       
      awaiter = Task.Delay(this.time).GetAwaiter();
       
      if (awaiter.IsCompleted)
       
      {
       
      goto Label_0077;
       
      }
       
      this._state = num = 0;
       
      this.taskAwaiter1 = awaiter;
       
      d__ = this;
       
      this._builder.AwaitUnsafeOnCompleted<TaskAwaiter, MyAsyncTest.GetUrlStringAsyncdStateMachine>(ref awaiter, ref d__);
       
      return;
       
      }
       
      awaiter = this.taskAwaiter1;
       
      this.taskAwaiter1 = new TaskAwaiter();
       
      this._state = num = -1;
       
      Label_0077:
       
      awaiter.GetResult();
       
      awaiter = new TaskAwaiter();
       
      //這里是異步方法await http.GetStringAsync(url);的具體實現
       
      TaskAwaiter<string> awaiter2 = this.http.GetStringAsync(this.url).GetAwaiter();
       
      if (awaiter2.IsCompleted)
       
      {
       
      goto Label_00EA;
       
      }
       
      this._state = num = 1;
       
      this.taskAwaiter2 = awaiter2;
       
      d__ = this;
       
      this._builder.AwaitUnsafeOnCompleted<TaskAwaiter<string>, MyAsyncTest.GetUrlStringAsyncdStateMachine>(ref awaiter2, ref d__);
       
      return;
       
      Label_00CD:
       
      awaiter2 = this.taskAwaiter2;
       
      this.taskAwaiter2 = new TaskAwaiter<string>();
       
      this._state = num = -1;
       
      Label_00EA:
       
      str2 = awaiter2.GetResult();
       
      awaiter2 = new TaskAwaiter<string>();
       
      this._str1 = str2;
       
      str = this._str1;
       
      }
       
      catch (Exception exception)
       
      {
       
      this._state = -2;
       
      this._builder.SetException(exception);
       
      return;
       
      }
       
      this._state = -2;
       
      this._builder.SetResult(str);
       
      }
       
       
       
      [DebuggerHidden]
       
      private void SetStateMachine(IAsyncStateMachine stateMachine)
       
      {
       
      }
       
       
       
      }
      View Code

      明顯多個異步等待執行的時候就是在不斷調用狀態機中的MoveNext()方法。經驗來至我們之前分析過的IEumerable,不過今天的這個明顯復雜度要高于以前的那個。猜測是如此,我們還是來驗證下事實:

       

      在起始方法 GetUrlStringAsync 第一次啟動狀態機 stateMachine._builder.Start(ref stateMachine); 

       確實是調用了 MoveNext 。因為_state的初始值是-1,所以執行到了下面的位置:

      繞了一圈又回到了 MoveNext 。由此,我們可以現象成多個異步調用就是在不斷執行MoveNext直到結束。

      說了這么久有什么意思呢,似乎忘記了我們的目的是要通過之前編寫的測試代碼來分析異步的執行邏輯的。

      再次貼出之前的測試代碼,以免忘記了。

       反編譯后代碼執行邏輯圖:

       當然這只是可能性較大的執行流程,但也有 awaiter.Iscompleted 為 true 的情況。其他可能的留著大家自己去琢磨吧。 

      posted on 2022-05-31 11:40  葉子牛牛  閱讀(85)  評論(0)    收藏  舉報

      主站蜘蛛池模板: 国产在线亚州精品内射| 日韩精品中文字幕有码| 国产乱码精品一区二区三上| 在线精品另类自拍视频| 午夜爽爽爽男女污污污网站| 国产中文字幕精品喷潮| 蜜臀av色欲a片无人一区| 亚洲成人动漫av在线| 92国产精品午夜福利免费| 免费视频一区二区三区亚洲激情 | 夜夜添狠狠添高潮出水| 中文午夜乱理片无码| 自慰无码一区二区三区| 亚洲av免费看一区二区| 久久久国产精品VA麻豆| 91中文字幕一区二区| 人人玩人人添人人澡超碰| 青青草国产线观看| 岑溪市| 水蜜桃av无码| 久久月本道色综合久久| 日本牲交大片免费观看| 无码a∨高潮抽搐流白浆| 日韩黄色av一区二区三区| 777久久精品一区二区三区无码 | 国产熟女一区二区三区四区| 中文字幕无码av不卡一区| 国产乱码精品一品二品| 国产人妻精品午夜福利免费| 亚洲精品综合久久国产二区 | 巴塘县| 狠狠色噜噜狠狠狠狠蜜桃| 免费现黄频在线观看国产| 92久久精品一区二区| 久久精品熟妇丰满人妻久久| 好爽毛片一区二区三区四| 吴忠市| 色欲av久久一区二区三区久| 亚洲日韩图片专区第1页 | 欧美乱码伦视频免费| 久久综合色最新久久综合色|