西西软件园多重安全检测下载网站、值得信赖的软件下载站!
软件
软件
文章
搜索

首页编程开发ASP.NET → async、await在ASP.NET中线程死锁的解决方法

async、await在ASP.NET中线程死锁的解决方法

相关软件相关文章发表评论 来源:西西整理时间:2013/5/13 9:31:18字体大小:A-A+

作者:西西TS点击:333次评论:0次标签: async

Asynchronous XXXV1.0安卓版
  • 类型:休闲益智大小:32.1M语言:英文 评分:10.0
  • 标签:
立即下载

C# 5.0中引入了async 和 await。这两个关键字可以让你更方便的按照同步的方式写出异步代码。也就是说使你更方便的异步编程。

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

第一步:将 VS2010 升级到 VS2010 sp1.

第二步:下载Async CTP,进行安装

第三步:为应用程序添加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中,微软为一些类提供了扩展,如下:

WebClient的扩展如下:

可以看到基本上为每个Download 都增加了一个XXXTaskAsync 的扩展方法。

async、await线程死锁的故事及解决方法:

早就听说.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后再无后续输出。运行结果:

看来“事与愿违”。那么这里怎么没有出现之前的问题呢?

提问:再将第25行改为Task.Run(() => Sleep(10)).Wait();这时候会输出什么呢,或者说step4的输出线程ID是多少?Task.Wait();和await不一样,它会阻塞当前线程(而不管内部逻辑是否开辟了新的线程)。运行结果:

可得step4仍运行在主线程。

线程死锁

引起线程死锁的原因有很多。在ASP.NET[ MVC]的场景中,涉及到一个概念就是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 }

解决方法:

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

调用方使用await调用async方法,而非GetResult、Task.Resul、Task.Wait;//第3行

使用client.PostAsync(uri, httpContent).Result.Content.ReadAsStringAsync().Result。//阻塞当前线程,而非将控制权返回给调用方,如前所述

    相关评论

    阅读本文后您有什么感想? 已有人给出评价!

    • 8 喜欢喜欢
    • 3 顶
    • 1 难过难过
    • 5 囧
    • 3 围观围观
    • 2 无聊无聊

    热门评论

    最新评论

    发表评论 查看所有评论(0)

    昵称:
    表情: 高兴 可 汗 我不要 害羞 好 下下下 送花 屎 亲亲
    字数: 0/500 (您的评论需要经过审核才能显示)