zoukankan      html  css  js  c++  java
  • 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(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
            }
        }
    }
    code_full


     

  • 相关阅读:
    UVA 11925 Generating Permutations 生成排列 (序列)
    UVA 1611 Crane 起重机 (子问题)
    UVA 11572 Unique snowflakes (滑窗)
    UVA 177 PaperFolding 折纸痕 (分形,递归)
    UVA 11491 Erasing and Winning 奖品的价值 (贪心)
    UVA1610 PartyGame 聚会游戏(细节题)
    UVA 1149 Bin Packing 装箱(贪心)
    topcpder SRM 664 div2 A,B,C BearCheats , BearPlays equalPiles , BearSorts (映射)
    UVA 1442 Cave 洞穴 (贪心+扫描)
    UVA 1609 Foul Play 不公平竞赛 (构(luan)造(gao)+递归)
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/4760220.html
Copyright © 2011-2022 走看看