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

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

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

      async、await在ASP.NET[ MVC]中之線程死鎖的故事

      早就聽說.Net4.5里有一對好基友async和await,今兒我迫不及待地拿過來爽了一把。尼瑪就悲劇了啊。

      場景重構

       1 public ActionResult Index(string ucode)
       2 {
       3     string userInfo = GetUserInfo(ucode).Result;
       4     ViewData["UserInfo"] = userInfo;
       5     return View();
       6 }
       7 
       8 async Task<string> GetUserInfo(string ucode)
       9 {
      10     HttpClient client = new HttpClient();
      11     var httpContent = new FormUrlEncodedContent(new Dictionary<string, string>()
      12     {
      13         {"ucode", ucode}
      14     });
      15     string uri = "http://www.xxxx.com/user/get";
      16     var response = await client.PostAsync(uri, httpContent);
      17     return response.Content.ReadAsStringAsync().Result;
      18 }

      上述代碼是對真實案例的簡化,即通過第三方OPenAPI獲取用戶信息,然后展示在Index頁中,很簡單。我點運行之后,發現執行到var response = await client.PostAsync(uri, httpContent);黃色小箭頭進入到這句代碼之后就消失的無影無蹤,我等了半宿,然后……然后就沒有然后了,沒有異常,只有寂寞。

      我首先考慮到是不是HttpClient引起的(之前使用HttpWebRequest.GetResponse能按預期執行,因此不會是http://www.xxxx.com/user/get這個API的問題,且當時并沒有想到會是線程問題),查閱了很多資料,對代碼進行反復修改,問題依舊。后來我鬼使神差地將最后兩行改為:

      1 var response = client.PostAsync(uri, httpContent).Result.Content.ReadAsStringAsync().Result;
      2 return response;

      問題竟然神奇的消失了,當Index頁面展現在我眼前的時候,我心說這不是玩我呢吧。我安慰自己說這或許是.NET框架的某個不為人知的bug,倒霉被我遇到,不管了洗洗睡吧。經過一個晚上的折騰,累得夠嗆,于是我很快就進入了夢鄉。夢中考英語,試卷上只能看到密密麻麻的a,我急得滿頭大汗,再仔細一看,滿滿的就兩個單詞:async和await。我一下驚醒了。

      async和await

      關于async和await,這兄弟倆是對異步編程的語法簡化。談到異步,就涉及到線程和邏輯執行順序,看下面代碼就一清二楚了。

       1 class Program
       2 {
       3     static void Main(string[] args)
       4     {
       5         Console.WriteLine("step1,線程ID:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
       6 
       7         AsyncDemo demo = new AsyncDemo();
       8         //demo.AsyncSleep().Wait();//Wait會阻塞當前線程直到AsyncSleep返回
       9         demo.AsyncSleep();//不會阻塞當前線程
      10 
      11         Console.WriteLine("step5,線程ID:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
      12         Console.ReadLine();
      13     }
      14 }
      15 
      16 public class AsyncDemo
      17 {
      18 
      19     public async Task AsyncSleep()
      20     {
      21         Console.WriteLine("step2,線程ID:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
      22 
      23         //await關鍵字表示“等待”Task.Run傳入的邏輯執行完畢,此時(等待時)AsyncSleep的調用方能繼續往下執行(準確地說,是當前線程不會被阻塞)
      24         //Task.Run將開辟一個新線程執行指定邏輯
      25         await Task.Run(() => Sleep(10));
      26 
      27         Console.WriteLine("step4,線程ID:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
      28     }
      29 
      30     private void Sleep(int second)
      31     {
      32         Console.WriteLine("step3,線程ID:{0}", System.Threading.Thread.CurrentThread.ManagedThreadId);
      33 
      34         Thread.Sleep(second * 1000);
      35     }
      36 
      37 }

      運行結果:

      注意step2和step4雖然在同一個方法內部,但它們的運行線程是不同的,step4與step3一樣使用Task.Run開辟的新線程。注意:假如我們在Sleep里再次使用Task.Run又開辟了新線程,假設ID為10,并通過await關鍵詞修飾,那么step4將運行在線程10。假如將第8、9行注釋互換:

      1 demo.AsyncSleep().Wait();//Wait會阻塞當前線程直到AsyncSleep返回
      2 //demo.AsyncSleep();//不會阻塞當前線程

      即人為控制異步邏輯同步返回,其實這和之前獲取用戶信息的場景是一樣一樣的,猜想是在執行step2或step3后再無后續輸出。運行結果:

      看來“事與愿違”。那么之前的出現的問題是怎么回事呢?既然step4和step1所在線程不一樣,我們能想到什么?當然是線程死鎖了!

      提問:再將第25行改為Task.Run(() => Sleep(10)).Wait();這時候會輸出什么呢,或者說step4的輸出線程ID是多少?Task.Wait();和await不一樣,它會阻塞當前線程(而不管內部邏輯是否開辟了新的線程)。運行結果:

      可得step4仍運行在主線程。

      線程死鎖

      引起線程死鎖的原因有很多。在ASP.NET[ MVC]的場景中,涉及到一個概念就是AspNetSynchronizationContext。AspNetSynchronizationContext出現在.NET Framework 2.0中,因為這個版本在 ASP.NET 體系結構中引入了異步頁面在 .NET Framework 2.0 之前的版本中,每個 ASP.NET 請求都需要一個線程,直到該請求完成。 這會造成線程利用率低下,因為頁面邏輯通常依賴于數據庫查詢和 Web 服務調用,并且處理請求的線程必須等待,直到所有這些操作結束。 使用異步頁面,處理請求的線程可以開始每個操作,然后返回到 ASP.NET 線程池,當操作結束時,ASP.NET 線程池的另一個線程可以完成該請求,AspNetSynchronizationContext在這個過程中扮演了異步操作周期維護員的角色(或許還發揮了其它作用)。當一個異步操作完成,需要依賴AspNetSynchronizationContext告知頁面,此時AspNetSynchronizationContext將未完成的異步操作數減1,并以同步方式處理異步線程發送過來的委托(即便是以Post“異步”方法),因此假如一個頁面請求有多個異步操作同時完成,每次也只能執行一個回調委托(不同委托執行的線程不知是否是同一個,however,執行線程將具有原始頁面的標識和區域)。綜上所述,同一個AspNetSynchronizationContext(不知道一個AspNetSynchronizationContext實例是針對單個請求還是整個應用程序同時只能最多被一個線程使用,結合async和await的特性,回到本文開頭的代碼:

       1 public ActionResult Index(string ucode)
       2 {
       3     string userInfo = GetUserInfo(ucode).Result;//線程A阻塞,等待GetUserInfo返回,當前上下文AspNetSynchronizationContext
       4     ViewData["UserInfo"] = userInfo;
       5     return View();
       6 }
       7 
       8 async Task<string> GetUserInfo(string ucode)
       9 {
      10     HttpClient client = new HttpClient();
      11     var httpContent = new FormUrlEncodedContent(new Dictionary<string, string>()
      12     {
      13         {"ucode", ucode}
      14     });
      15     string uri = "http://www.xxxx.com/user/get";     //client.PostAsync在其內部開辟新線程(設為B)異步執行,注意await并不會阻塞當前線程,而是將控制權返回方法調用方,這里是Index Action
      16     var response = await client.PostAsync(uri, httpContent);     //client.PostAsync返回,但下列代碼仍運行在線程B。當前方法企圖重入AspNetSynchronizationContext,死鎖產生在這里
      17     return response.Content.ReadAsStringAsync().Result;
      18 }

       解決方法:

      1. var response= await client.PostAsync(uri, httpContent).ConfigureAwait(false);//第16行
      2. 調用方使用await調用async方法,而非GetResult、Task.Resul、Task.Wait;//第3行
      3. 使用client.PostAsync(uri, httpContent).Result.Content.ReadAsStringAsync().Result。//阻塞當前線程,而非將控制權返回給調用方,如前所述

      參考資料


       

      后記

      await關鍵字并不表示后續代碼馬上在新線程上執行,是否開辟線程取決于是否真正創建了Task(or 從Task池中取得)。運行下面代碼:

       1 class Program
       2 {
       3     static void Main(string[] args)
       4     {
       5         Console.WriteLine($"1:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
       6         TestTransfer1();
       7         Console.WriteLine($"8:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
       8         Console.ReadLine();
       9     }
      10 
      11     static async void TestTransfer1()
      12     {
      13         Console.WriteLine($"2:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      14         await TestTransfer2();
      15         Console.WriteLine($"7:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      16     }
      17 
      18     static async Task TestTransfer2()
      19     {
      20         Console.WriteLine($"3:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      21         await Test();
      22         Console.WriteLine($"6:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      23     }
      24 
      25     static async Task Test()
      26     {
      27         Console.WriteLine($"4:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      28         await Task.Run(() => Sleep(5)); //此處之后才開辟了新線程
      29         Console.WriteLine($"5:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      30     }
      31 
      32     static void Sleep(int second)
      33     {
      34         Thread.Sleep(second * 1000);
      35     }
      36 }

      運行結果:

      一目了然,所以我們不需要擔心多級方法調用時會創建眾多線程并切換導致的性能問題。

      .NET平臺提供的異步方法一般都會new或get一個Task,因此會如上代碼一樣遇到這些方法,后續邏輯會切換到新線程上運行。需要注意的是.NET可能會在某些方面做一些優化,比如以同步方式完成此類方法,比如StreamWriter.WriteLineAsync方法,我測試了之后還是運行在原線程,maybe其內部是根據寫入字符多少決定是否切換線程,這就不深究了。

      關于是否在await后才開始真正執行異步方法,改造上面代碼如下:

       1 class Program
       2     {
       3         static void Main(string[] args)
       4         {
       5             TestTransfer1();
       6             Console.ReadLine();
       7         }
       8 
       9         static async void TestTransfer1()
      10         {
      11             Console.WriteLine($"1:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      12             var task = Test();            
      13             Sleep(2);
      14             Console.WriteLine($"4:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      15             await task;
      16         }
      17 
      18         static async Task Test()
      19         {
      20             Console.WriteLine($"2:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      21             await Task.Run(() => Sleep(1)); //此處之后才開辟了新線程
      22             Console.WriteLine($"3:Thread.CurrentThread.ManagedThreadId-{Thread.CurrentThread.ManagedThreadId}");
      23         }
      24 
      25         static void Sleep(int second)
      26         {
      27             Thread.Sleep(second * 1000);
      28         }
      29     }

      運行結果:

      可知在獲取task實例時,異步操作就開始了,而不需要等await。由于這個特性,我們可以發起多個沒有順序依賴關系的task,最后再統一await它們,提高效率,比如分頁:

      var task_totalcount = query.CountAsync();               
      query = query.OrderBy(sortfield, sortorder);
      query = query.Skip(startindex).Take(takecount);
      var task_getdata = query.ToListAsync();
      
      result.TotalCount = await task_totalcount;
      result.Data = await task_getdata;
      
      return result;

       

      參考資料:

      C#與C++的發展歷程第三 - C#5.0異步編程巔峰

       

      轉載請注明本文出處:http://www.rzrgm.cn/newton/archive/2013/05/13/3075039.html

      posted @ 2013-05-13 09:09  萊布尼茨  閱讀(7210)  評論(17)    收藏  舉報
      主站蜘蛛池模板: 少妇被无套内谢免费看| 国产成人av乱码在线观看| 黄色三级亚洲男人的天堂| 国产精品午夜福利在线观看| 国产99久久无码精品| 亚洲成人高清av在线| 人妻少妇精品无码专区| 日韩幕无线码一区中文| 国产区图片区小说区亚洲区| 亚洲综合一区二区三区不卡| 国产精品国产三级国快看| 欧美日韩中文字幕久久伊人| 一道本AV免费不卡播放| 日韩精品在线观看一二区| chinese极品人妻videos| 国产一区二区四区不卡| 绝顶丰满少妇av无码| 国产精品美人久久久久久AV| 福利一区二区视频在线| 欧美乱码卡一卡二卡四卡免费| 永久免费AV无码国产网站 | 日韩精品无码区免费专区| 亚洲国产色婷婷久久99精品91| 精品人妻系列无码人妻漫画| 国产成人精品视频不卡| 青青草无码免费一二三区| 国产a在亚洲线播放| 久久中文字幕一区二区| 国产在线观看91精品亚瑟| 青青草原网站在线观看| 久久精品久久电影免费理论片| 亚洲 中文 欧美 日韩 在线| 欧美成人精品手机在线| 亚州中文字幕一区二区| 亚洲区中文字幕日韩精品| 亚洲乱理伦片在线观看中字| 无码人妻斩一区二区三区| 国产老熟女国语免费视频| 国产精品久久久久久影视| 亚洲日韩AV秘 无码一区二区| 综合区一区二区三区狠狠|