问题

首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁。例:

        public ActionResult Asv2()
{
//dead lock
var task = AssignValue2();
task.Wait();
return Content(_container);
} private void Assign()
{
_container = "Hello World";
} public async Task AssignValue2()
{
await Task.Delay();
await Task.Run(() => Assign());
}

这是由于async方法注册的回调要求返回到调用async的线程——而在主线程(action方法所在线程)中又对Task执行了Wait(),相互等待,导致了死锁。

Wait一个async方法是否一定导致死锁(ASP.NET MVC)

否。Task类型的对象有一个ConfigureAwait方法,将参数置为false可以防止回调返回当前线程。但是有一个前提条件:async方法的调用链中的所有async方法都必须指定ConfigureAwait(false)。例:

        public async Task AssignValue2()
{
await Task.Delay();
await Task.Run(() => Assign());
} public async Task AssignValue()
{
await Task.Run(() => Assign()).ConfigureAwait(false);
} public async Task Wrapped()
{
await AssignValue().ConfigureAwait(false);
} public async Task Wrapped2()
{
await AssignValue2().ConfigureAwait(false);
} public async Task Update()
{
await Task.Run(() => { _container = _container + "Hello "; });
} public async Task Update2()
{
await Task.Run(() => { _container = _container + "World"; });
} #region 基本调用 /// <summary>
/// 调用层次最深的异步方法(第一个调用的异步方法)不要求返回到主线程
/// </summary>
/// <returns></returns>
public ActionResult Asv1()
{
var task = AssignValue();
task.Wait();
return Content(_container);
} /// <summary>
/// ...返回主线程
/// </summary>
/// <returns></returns>
public ActionResult Asv2()
{
//dead lock
var task = AssignValue2();
task.Wait();
return Content(_container);
} /// <summary>
/// 所有都不要求返回主线程
/// </summary>
/// <returns></returns>
public ActionResult Wrp()
{
var task = Wrapped();
task.Wait();
return Content(_container);
} /// <summary>
/// 其中一个要求返回主线程
/// </summary>
/// <returns></returns>
public ActionResult Wrp2()
{
//dead lock
var task = Wrapped2();
task.Wait();
return Content(_container);
} #endregion

对比

可以看到,在Asv()与Wrp()中,分别直接Wait了AssignValue()与Wrapped()返回的Task,并没有造成死锁。因为这两个方法的调用链中的所有的async操作都被配置为不返回当前线程(Action所在线程)。另外两个标记为2的对比方法则不然,结果也相反。

对async调用链的一点理解

首先,await之后的代码,是作为回调,被注册到一个Awaitor对象中。其次,async中的Task是被成为promised风格的,也就是,被await的async方法承诺:“我会来回调你后面的这些逻辑(不是现在)”。那么对于以下的伪代码:

        public async Task A()
{
await Task.Run(() => { });
} public async Task B()
{
await A();
//B.L
} public async Task C()
{
await B();
//C.L
}

如果我们调用了C(),运行期间的事情是:运行B()->运行A(),然后:将//B.L部分代码注册到A的回调当中->将//C.L部分代码注册到B的回调当中。也就是说,await之前的操作和注册的操作都是在当前线程完成的。那么,如果没有ConfigureAwait(false),所有的回调操作都会期望返回到主线程。所以会导致各种线程死锁。

总的来说,async这个关键字像是给C#开了点了新技能吧,以非常清新的方式就让方法“天然”支持了异步(想想各种StartNew各种ContinueWith,嵌套层次一深的时候,那简直...)。另外,ContinueWith会切换线程,也会带来开销。

在同步方法中Wait

async与await几乎是自成体系的,只要await一个async方法,就会被要求将本方法标记为async,随着不断地接触,个人感觉这是可以理解的(然而我解释不来)。

根据上面的分析,之所以会导致线程锁,主要原因是回调要求返回到调用线程(主线程),而作为一个同步方法,主线程必然是要等待的。所以解决方案也比较明确:想办法别让回调返回到主线程——即:在另外一个线程中调用async方法。先看看失败的例子:

        #region 次线程创建,主线程wait
//高概率 dead lock public ActionResult TcreateMwait()
{
Task task = null;
Task.Run(() => { task = AssignValue2(); }).Wait();
task.Wait();
return Content(_container);
} public ActionResult TcreateMunwait()
{
//主线程不等待的对比组
Task task = null;
Task.Run(() => { task = AssignValue2(); }).Wait();
return Content(_container);
} #endregion

我无法理解为何这个会失败——肯定是我对Task以及线程的理解有问题,我回去补补课,这个先放这里。然后是成功的例子:

#region 次线程创建,次次线程wait(continue with),主线程wait次次线程

        public ActionResult Twait()
{
Task task = null;
Task.Run(() => { task = AssignValue(); })
.ContinueWith(token => task.Wait())
.Wait();
return Content(_container);
} public ActionResult Twait2()
{
Task.Run(() => AssignValue2())
.ContinueWith(task => { task.Wait(); })
.Wait();
return Content(_container);
} public ActionResult Swait()
{
AsyncHelper.InvokeAndWait(AssignValue2);
return Content(_container);
} #endregion

然后,这里提供了一个辅助方法:

        public static void InvokeAndWait(Func<Task> asyncMethod)
{
Task.Run(() => asyncMethod())
.ContinueWith(task => task.Wait())
.Wait();
}

小插曲:Resharp会提示你把()=>asyncMethod()直接使用asyncMethod代替,别信。

最后是对这个辅助方法的一些测试:

       #region waitsafely examples

        public ActionResult Inorder()
{
AsyncHelper.InvokeAndWait(Update);
AsyncHelper.InvokeAndWait(Update2);
return Content(_container);
} public ActionResult NotInOrder()
{
AsyncHelper.InvokeAndWait(() => Task.WhenAll(Update(), Update2()));
return Content(_container);
} #endregion

最后,这里是使用的所有代码,欢迎指点:

namespace Dpfb.Manage.Controllers
{
public class AsyncsController : Controller
{
private void Assign()
{
_container = "Hello World";
} private static string _container; protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
_container = string.Empty;
} public async Task AssignValue2()
{
await Task.Delay();
await Task.Run(() => Assign());
} public async Task AssignValue()
{
await Task.Run(() => Assign()).ConfigureAwait(false);
} public async Task Wrapped()
{
await AssignValue().ConfigureAwait(false);
} public async Task Wrapped2()
{
await AssignValue2().ConfigureAwait(false);
} public async Task Update()
{
await Task.Run(() => { _container = _container + "Hello "; });
} public async Task Update2()
{
await Task.Run(() => { _container = _container + "World"; });
} #region 基本调用 /// <summary>
/// 调用层次最深的异步方法(第一个调用的异步方法)不要求返回到主线程
/// </summary>
/// <returns></returns>
public ActionResult Asv1()
{
var task = AssignValue();
task.Wait();
return Content(_container);
} /// <summary>
/// ...返回主线程
/// </summary>
/// <returns></returns>
public ActionResult Asv2()
{
//dead lock
var task = AssignValue2();
task.Wait();
return Content(_container);
} /// <summary>
/// 所有都不要求返回主线程
/// </summary>
/// <returns></returns>
public ActionResult Wrp()
{
var task = Wrapped();
task.Wait();
return Content(_container);
} /// <summary>
/// 其中一个要求返回主线程
/// </summary>
/// <returns></returns>
public ActionResult Wrp2()
{
//dead lock
var task = Wrapped2();
task.Wait();
return Content(_container);
} #endregion #region 次线程创建,次次线程wait(continue with),主线程wait次次线程 public ActionResult Twait()
{
Task task = null;
Task.Run(() => { task = AssignValue(); })
.ContinueWith(token => task.Wait())
.Wait();
return Content(_container);
} public ActionResult Twait2()
{
Task.Run(() => AssignValue2())
.ContinueWith(task => { task.Wait(); })
.Wait();
return Content(_container);
} public ActionResult Swait()
{
AsyncHelper.InvokeAndWait(AssignValue2);
return Content(_container);
} #endregion #region 次线程创建,主线程wait
//高概率 dead lock public ActionResult TcreateMwait()
{
Task task = null;
Task.Run(() => { task = AssignValue2(); }).Wait();
task.Wait();
return Content(_container);
} public ActionResult TcreateMunwait()
{
//主线程不等待的对比组
Task task = null;
Task.Run(() => { task = AssignValue2(); }).Wait();
return Content(_container);
} #endregion #region waitsafely examples public ActionResult Inorder()
{
AsyncHelper.InvokeAndWait(Update);
AsyncHelper.InvokeAndWait(Update2);
return Content(_container);
} public ActionResult NotInOrder()
{
AsyncHelper.InvokeAndWait(() => Task.WhenAll(Update(), Update2()));
return Content(_container);
} #endregion public async Task A()
{
await Task.Run(() => { });
} public async Task B()
{
await A();
//B.L
} public async Task C()
{
await B();
//C.L
}
}
}

code_full

ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法的更多相关文章

  1. MVC 如何在一个同步方法(非async)方法中等待async方法

    MVC 如何在一个同步方法(非async)方法中等待async方法 问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public Actio ...

  2. ASP.NET MVC轻教程 Step By Step 7——改进Write动作方法

    在上一节我们使用强类型视图改进Write视图获得更好的智能感知和代码重构,现在可以进一步的改进动作方法. Step 1. 数据模型绑定 在Save方法中我们使用Request来获取表单传送的值,其实可 ...

  3. 总结ASP.NET MVC Web Application中将数据显示到View中的几种方式

    当我们用ASP.NET MVC开发Web应用程序的时候,我们都是将需要呈现的数据通过"Controllers"传输到"View"当中,怎么去实现,下面我介绍一下 ...

  4. 【ASP.NET MVC系列】浅谈表单和HTML辅助方法

    [01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作篇)(下) [04]浅谈ASP. ...

  5. Asp.Net MVC 从客户端<a href="http://www....")中检测到有潜在危险的 Request.Form 值

    Asp.Net MVC应用程序, Framework4.0: 则需要在webconfig文件的 <system.web> 配置节中加上 <httpRuntime requestVal ...

  6. asp.net mvc 3.0 知识点整理 ----- (2).Controller中几种Action返回类型对比

    通过学习,我们可以发现,在Controller中提供了很多不同的Action返回类型.那么具体他们是有什么作用呢?它们的用法和区别是什么呢?通过资料书上的介绍和网上资料的查询,这里就来给大家列举和大致 ...

  7. 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]

    前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...

  8. ASP.NET MVC 提示there was error getting the type的解决方法

    在MVC中根据模型类创建控制器时提示there was error getting the type的原因是你新建的这个类模型文件后没有重新生成,先重新生成项目就可以添加控制器了.

  9. asp.net mvc 数据查询赋值到文本框中

    大家做了很多文本框查询并且赋值回来 1.先是把数据对象查询结果后台,然后把对象赋值给对象在赋值回来前台页面 2.使用@html helerper 数据查询,使用 ViewContext.RouteDa ...

随机推荐

  1. PHP高级笔记汇总

    一.PHP日期 PHP的date()函数用于格式化时间或日期.PHP Date()函数可把时间戳格式化为可读性更好的日期和时间.语法:date(format,timestamp)format:必需.规 ...

  2. "/Date(1405056837780)/" 时间转换

    //往往json传过来的时间都是"/Date(1405056837780)/" //转换需要的方法 String.prototype.ToString = function (fo ...

  3. 网页打包安卓APP流程

    搭建环境过程: 1. 安装JDK. 参见http://www.cnblogs.com/Li-Cheng/p/4334985.html. 注:实质上到该网址上下载好JDK安装包,安装后添加一个环境变量: ...

  4. PHP伪造referer突破防盗链

    php伪造referer实例代码,主要用于一些突破防盗链. 可以从这个例子中发展出很多的应用.比如隐藏真实的URL地址……嘿嘿,具体的就自己分析去吧 这里新建一个文件file.php.后面的参数就是需 ...

  5. Java 第五天 Spring IOC 配置文件

    Spring xml结构定义文件: http://www.springframework.org/schema/beans/spring-beans.xsd 可用xsd列表: http://www.s ...

  6. Requests:Python HTTP Module学习笔记(一)(转)

    Requests:Python HTTP Module学习笔记(一) 在学习用python写爬虫的时候用到了Requests这个Http网络库,这个库简单好用并且功能强大,完全可以代替python的标 ...

  7. WPF数据双向绑定

    设置双向绑定,首先控件要绑定的对象要先继承一个接口: INotifyPropertyChanged 然后对应被绑定的属性增加代码如下: 意思就是当Age这个属性变化时,要通知监听它变化的人. 即:Pr ...

  8. 说说oracle中的面向对象与面向集合

    这一篇算是对近期自己学习的一个心得总结 一.oracle的面向对象 SQL是面向集合的这个大家都知道,但是不可否认现在的oracle中有很多地方都体现着面向对象的思维.(这也算是各大语言殊途同归的一个 ...

  9. memcached 简介

    最近,想看看开源的东西,正好在网上看到了memcached这个服务器,就简单学了学.做个笔记! 1.memcached 介绍 memcached我原本以为是一款数据库软件,但详细了解才发现,准确的是一 ...

  10. JavaScript高级程序设计之基本包装类型

    为便于操作基本类型值,ECMAScript提供了3个特殊的引用类型:Boolean, Number 和 String // 字符串怎么会有方法呢 var str1 = "some text& ...