ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法
问题
首先,在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方法的更多相关文章
- MVC 如何在一个同步方法(非async)方法中等待async方法
MVC 如何在一个同步方法(非async)方法中等待async方法 问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public Actio ...
- ASP.NET MVC轻教程 Step By Step 7——改进Write动作方法
在上一节我们使用强类型视图改进Write视图获得更好的智能感知和代码重构,现在可以进一步的改进动作方法. Step 1. 数据模型绑定 在Save方法中我们使用Request来获取表单传送的值,其实可 ...
- 总结ASP.NET MVC Web Application中将数据显示到View中的几种方式
当我们用ASP.NET MVC开发Web应用程序的时候,我们都是将需要呈现的数据通过"Controllers"传输到"View"当中,怎么去实现,下面我介绍一下 ...
- 【ASP.NET MVC系列】浅谈表单和HTML辅助方法
[01]浅谈Google Chrome浏览器(理论篇) [02]浅谈Google Chrome浏览器(操作篇)(上) [03]浅谈Google Chrome浏览器(操作篇)(下) [04]浅谈ASP. ...
- Asp.Net MVC 从客户端<a href="http://www....")中检测到有潜在危险的 Request.Form 值
Asp.Net MVC应用程序, Framework4.0: 则需要在webconfig文件的 <system.web> 配置节中加上 <httpRuntime requestVal ...
- asp.net mvc 3.0 知识点整理 ----- (2).Controller中几种Action返回类型对比
通过学习,我们可以发现,在Controller中提供了很多不同的Action返回类型.那么具体他们是有什么作用呢?它们的用法和区别是什么呢?通过资料书上的介绍和网上资料的查询,这里就来给大家列举和大致 ...
- 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...
- ASP.NET MVC 提示there was error getting the type的解决方法
在MVC中根据模型类创建控制器时提示there was error getting the type的原因是你新建的这个类模型文件后没有重新生成,先重新生成项目就可以添加控制器了.
- asp.net mvc 数据查询赋值到文本框中
大家做了很多文本框查询并且赋值回来 1.先是把数据对象查询结果后台,然后把对象赋值给对象在赋值回来前台页面 2.使用@html helerper 数据查询,使用 ViewContext.RouteDa ...
随机推荐
- 用户 'sa' 登录失败。 (Microsoft SQL Server,错误: 18456)
今天登陆数据库的时候,却忽然登陆了不了,并且提示了这样的错: 解决方法: 1.用Windows身份登录数据库 2.安全性==>登录名==>双击sa 3.重设密码 4.状态==>登录: ...
- wamp+zendstudio12+xDebugger环境搭建
1.网上找到Wampserver,下载即可,一路安装即可,安装好后打开浏览器输入http://localhost即可看到有没有安装成功 比如我的是这样的,就说嘛wampserver安装成功了 wamp ...
- Linux文件系统的主要目录结构说明及分区方案
Linux操作系统有一些固定的目录.各种Linux发行版的目录结构虽然不会一模一样,但是不会有很大差异.知道了这些目录的作用,不仅对你进行磁盘分区规划很有帮助,而且会让你以后的日常维护工作变得轻松.只 ...
- mongodb 修改数据结构的一个例子以及小梳理
mongodb的存储结构是灵活可变的,但是,并不意味着我们就肆意地使用不规则的文档结构.不规则的文档结构对于开发和后期的维护都是一个灾难.所以,还是要有一个约定的格式. 但是,由于前期设计的不周详和其 ...
- 12)Java Constructor
Constructor 构造器Constructor不能被继承,因此不能重写Overriding,但可以被重载Overloading. 构造器用来确保每个对象都会得到初始化.当对 ...
- Winform开发几个常用的开发经验及知识积累(一)
本人做Winform开发多年,孜孜不倦,略有小成,其中收集或者自己开发一些常用的东西,基本上在各个项目都能用到的一些开发经验及知识积累,现逐步介绍一些,以飨读者,共同进步. 1.窗口[×]关闭按钮变为 ...
- PuTTY 中文教程
PuTTY 中文教程 更新记录 2006-11-29初步完成想写的这些东西 2007-06-11PuTTY 的最新版本到了0.6:修改了一下 SSH 隧道:添加了 SSH 反向隧道:添加了用 SSH ...
- Ztack学习笔记(4)-系统网络分析
协调器的组网,终端设备和路由设备发现网络以及加入网络 //第一步:Z-Stack 由 main()函数开始执行,main()函数共做了 2 件事:一是系统初始化,另外一件是开始执行轮转查询式操作系统 ...
- 删除redo所有日志,数据库无法启动
半夜在itpub上看到有人发贴,说不小心删除了redo所有日志,导致数据库无法启动,因此模拟了一下. 如下: OS: Oracle Linux Server release 5.7 DB: O ...
- hdu 4417 Super Mario/树套树
原题链接:http://acm.hdu.edu.cn/showproblem.php?pid=4417 题意很简单,给定一个序列求一个区间 [L, R,]中小于等于H的元素的个数. 好像函数式线段树可 ...