西西軟件園多重安全檢測(cè)下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁編程開發(fā)ASP.NET → async、await在ASP.NET中線程死鎖的解決方法

async、await在ASP.NET中線程死鎖的解決方法

相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來源:西西整理時(shí)間:2013/5/13 9:31:18字體大。A-A+

作者:西西TS點(diǎn)擊:333次評(píng)論:0次標(biāo)簽: async

Asynchronous XXXV1.0安卓版
  • 類型:休閑益智大。32.1M語言:英文 評(píng)分:10.0
  • 標(biāo)簽:
立即下載

C# 5.0中引入了async 和 await。這兩個(gè)關(guān)鍵字可以讓你更方便的按照同步的方式寫出異步代碼。也就是說使你更方便的異步編程。

下面演示使用async,await的方式

第一步:將 VS2010 升級(jí)到 VS2010 sp1.

第二步:下載Async CTP,進(jìn)行安裝

第三步:為應(yīng)用程序添加AsyncCTPLibrary引用,如下:

OK,將上面的SumPageSizes 方法修改如下:

public async Task<int> SumPageSizesAsync2(IList<Uri> uris){    var tasks = uris.Select(uri => new WebClient().DownloadDataTaskAsync(uri));    var data = await TaskEx.WhenAll(tasks);    return await TaskEx.Run(() => 
    {        return data.Sum(s => s.Length);    });}

在AsyncCTPLibrary.dll中,微軟為一些類提供了擴(kuò)展,如下:

WebClient的擴(kuò)展如下:

可以看到基本上為每個(gè)Download 都增加了一個(gè)XXXTaskAsync 的擴(kuò)展方法。

async、await線程死鎖的故事及解決方法:

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

場(chǎng)景重構(gòu)

 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 }

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

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

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

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

async和await

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

 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會(huì)阻塞當(dāng)前線程直到AsyncSleep返回
 9         demo.AsyncSleep();//不會(huì)阻塞當(dāng)前線程
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關(guān)鍵字表示等待Task.Run傳入的邏輯執(zhí)行完畢,此時(shí)(等待時(shí))AsyncSleep的調(diào)用方能繼續(xù)往下執(zhí)行
24         //Task.Run將開辟一個(gè)新線程執(zhí)行指定邏輯
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 }

運(yùn)行結(jié)果:

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

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

即人為控制異步邏輯同步返回,其實(shí)這和之前獲取用戶信息的場(chǎng)景是一樣一樣的,猜想是在執(zhí)行step2或step3后再無后續(xù)輸出。運(yùn)行結(jié)果:

看來“事與愿違”。那么這里怎么沒有出現(xiàn)之前的問題呢?

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

可得step4仍運(yùn)行在主線程。

線程死鎖

引起線程死鎖的原因有很多。在ASP.NET[ MVC]的場(chǎng)景中,涉及到一個(gè)概念就是AspNetSynchronizationContext,它同時(shí)只能被一個(gè)線程獨(dú)占。結(jié)合async和await的特性,回到本文開頭的代碼:

 1 public ActionResult Index(string ucode)
 2 {
 3     string userInfo = GetUserInfo(ucode).Result;//線程A阻塞,等待GetUserInfo返回,當(dāng)前上下文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在其內(nèi)部開辟新線程(設(shè)為B)異步執(zhí)行,注意await并不會(huì)阻塞當(dāng)前線程,而是將控制權(quán)返回方法調(diào)用方,這里是Index Action
16     var response = await client.PostAsync(uri, httpContent);     //client.PostAsync返回,但下列代碼仍運(yùn)行在線程B。當(dāng)前方法企圖重入AspNetSynchronizationContext,死鎖產(chǎn)生在這里17     return response.Content.ReadAsStringAsync().Result;
18 }

解決方法:

var response= await client.PostAsync(uri, httpContent).ConfigureAwait(false);//第16行

調(diào)用方使用await調(diào)用async方法,而非GetResult、Task.Resul、Task.Wait;//第3行

使用client.PostAsync(uri, httpContent).Result.Content.ReadAsStringAsync().Result。//阻塞當(dāng)前線程,而非將控制權(quán)返回給調(diào)用方,如前所述

    相關(guān)評(píng)論

    閱讀本文后您有什么感想? 已有人給出評(píng)價(jià)!

    • 8 喜歡喜歡
    • 3 頂
    • 1 難過難過
    • 5 囧
    • 3 圍觀圍觀
    • 2 無聊無聊

    熱門評(píng)論

    最新評(píng)論

    發(fā)表評(píng)論 查看所有評(píng)論(0)

    昵稱:
    表情: 高興 可 汗 我不要 害羞 好 下下下 送花 屎 親親
    字?jǐn)?shù): 0/500 (您的評(píng)論需要經(jīng)過審核才能顯示)