ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含7249字,纯文字阅读大概需要11分钟。
内容图文
![ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法](/upload/InfoBanner/zyjiaocheng/1222/1afbc3b9fe564007b183a0986a1329d6.jpg)
问题
首先,在ASP.NET MVC 环境下对async返回的Task执行Wait()会导致线程死锁。例:
public ActionResult Asv2() { // dead lock var task = AssignValue2(); task.Wait(); return Content(_container); } privatevoid Assign() { _container = "Hello World"; } publicasync 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)。例:
![技术分享](/upload/getfiles/default/2022/11/12/20221112112753507.jpg)
![技术分享](/upload/getfiles/default/2022/11/12/20221112112753526.jpg)
public async Task AssignValue2() { await Task.Delay(500); await Task.Run(() => Assign()); } publicasync Task AssignValue() { await Task.Run(() => Assign()).ConfigureAwait(false); } publicasync Task Wrapped() { await AssignValue().ConfigureAwait(false); } publicasync Task Wrapped2() { await AssignValue2().ConfigureAwait(false); } publicasync Task Update() { await Task.Run(() => { _container = _container + "Hello "; }); } publicasync 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 lockvar 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 lockvar 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(() => { }); } publicasync Task B() { await A(); //B.L } publicasync 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 lockpublic 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
最后,这里是使用的所有代码,欢迎指点:
![技术分享](/upload/getfiles/default/2022/11/12/20221112112753507.jpg)
![技术分享](/upload/getfiles/default/2022/11/12/20221112112753526.jpg)
namespace Dpfb.Manage.Controllers { public class AsyncsController : Controller { private void Assign() { _container = "Hello World"; } privatestaticstring _container; protectedoverridevoid OnActionExecuting(ActionExecutingContext filterContext) { _container = string.Empty; } publicasync Task AssignValue2() { await Task.Delay(500); await Task.Run(() => Assign()); } publicasync Task AssignValue() { await Task.Run(() => Assign()).ConfigureAwait(false); } publicasync Task Wrapped() { await AssignValue().ConfigureAwait(false); } publicasync Task Wrapped2() { await AssignValue2().ConfigureAwait(false); } publicasync Task Update() { await Task.Run(() => { _container = _container + "Hello "; }); } publicasync 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 lockvar 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 lockvar 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 lockpublic 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); } #endregionpublicasync Task A() { await Task.Run(() => { }); } publicasync Task B() { await A(); //B.L } publicasync Task C() { await B(); //C.L } } }
原文:http://www.cnblogs.com/lightluomeng/p/4760220.html
内容总结
以上是互联网集市为您收集整理的ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法全部内容,希望文章能够帮你解决ASP.NET MVC 如何在一个同步方法(非async)方法中等待async方法所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。