MVC 如何在一个同步方法(非async)方法中等待async方法
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(500);
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(500);
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(500);
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
}
}
}
MVC 如何在一个同步方法(非async)方法中等待async方法的更多相关文章
- ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法
问题 首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁.例: public ActionResult Asv2() { //dead lock var t ...
- QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)
前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...
- 【MVC - 参数原理】详解SpringMVC中Controller的方法中参数的工作原理[附带源码分析]
前言 SpringMVC是目前主流的Web MVC框架之一. 如果有同学对它不熟悉,那么请参考它的入门blog:http://www.cnblogs.com/fangjian0423/p/spring ...
- java 对象的this使用 java方法中参数传递特性 方法的递归
一.this关键字,使用的情形,以及如何使用. 1.使用的情形 类中的方法体中使用this --初始化该对象 类的构造器中使用this --引用,调用该方法的对象 2.不写this,调用 只要方法或 ...
- springMVC在普通方法中调用service方法
SpringContextUtil类 package com.common.util; import org.springframework.beans.BeansException;import o ...
- 如何使用canvas绘制椭圆,扩展非chrome浏览器中的ellipse方法
这篇博文主要针对浏览器中绘制椭圆的方法扩展.在网上搜索了很多,发现他们绘制椭圆的方式都有缺陷.其中有压缩法,计算法,贝塞尔曲线法等多种方式.但是都不能很好的绘制出椭圆.所有我就对这个绘制椭圆的方式进行 ...
- java排序方法中的插入排序方法
插入排序方法就是:将一个数据插入到已经排好序的有序数据中,从而得到一个新的.个数加一的有序数据. package Array; //插入排序方法 import java.until.Scanner; ...
- java数组中的三种排序方法中的冒泡排序方法
我记得我大学学java的时候,怎么就是搞不明白这三种排序方法,也一直不会,现在我有发过来学习下这三种方法并记录下来. 首先说说冒泡排序方法:冒泡排序方法就是把数组中的每一个元素进行比较,如果第i个元素 ...
- (1)定义一个接口CanFly,描述会飞的方法public void fly(); (2)分别定义类飞机和鸟,实现CanFly接口。 (3)定义一个测试类,测试飞机和鸟,在main方法中创建飞机对象和鸟对象, 再定义一个makeFly()方法,其中让会飞的事物飞。并在main方法中调用该方法, 让飞机和鸟起飞。
package b; public interface CanFly { public void fly(); } package b; public class FeiJi implements C ...
随机推荐
- android 泰国/缅甸语/捷克较低,Contacts联系出现精神错乱之类的问题清单
更改ContactsProvider2.java文件 public static final String SECTION_HEADING = "SUBSTR(%1$s,1,1)" ...
- Coreseek:部门查询和增量索引代替实时索引
1.行业调查 索引系统需要通过主查询来获取所有的文档信息,一个简单的实现是整个表的数据到内存,但是这可能会导致整个表被锁定,并且使其它操作被阻止(例如:在MyISAM格款式上INSERT操作).同时, ...
- 使用MVC写模式jsp连接到数据库操作
首先用一个JavaBean封装数据库操作,即mvc中的模型 JdbcBean.java package data; import java.sql.*; public class JdbcBean { ...
- 用MODELLER构建好模型后对loop区域进行自动的优化过程
一:对生成的模型的所有的loop区域进行优化 # Homology modeling by the automodel class from modeller import * from modell ...
- 利用ffmpeg将H264解码为RGB
因为公司买到了一个不提供解码器的设备,我不得已还要做解码的工作.在网上找了一圈,H264解码比較方便的也就是ffmpeg一系列的函数库了,原本设备中也是用这套函数库解码,但厂家不给提供,没办法,仅仅得 ...
- HDoj-2084-号码塔-dp
号码塔 Time Limit: 1000/1000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others) Total Submis ...
- Spring4.0MVC学习资料,ApplicationContext中的方法具体解释(三)
做为java开源的一部分,spring框架一直排在老大的位置.Spring4.0 是 Spring 推出的一个重大版本号升级,进一步加强了 Spring 作为 Java 领域第一开源平台的地位.Spr ...
- CareerCup Chapter 4 Trees and Graphs
struct TreeNode{ int val; TreeNode* left; TreeNode* right; TreeNode(int val):val(val),left(NULL),rig ...
- 如何更改Java括号中的默认对齐
(1)在使用程序猿非常Java当大括号的排列有感就是它的不那么整齐! 很多人不知道为什么会这样: public class HelloWorld{ pulic static void main(Str ...
- 【转】c#实现字符串倒序的n种写法
其中LINQ写法最为简洁 //string concatenation with for loop public string ReverseA(string text) { char[] c ...