• 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


     

  • 相关阅读:
    魔法方法中的__str__和__repr__区别
    新建分类目录后,点击显示错误页面?
    3.用while和for循环分别计算100以内奇数和偶数的和,并输出。
    2.for循环实现打印1到10
    1.while循环实现打印1到10
    021_for语句
    014_运算符_字符串连接
    020_while语句
    019_增强switch语句
    018_switch语句
  • 原文地址:https://www.cnblogs.com/lightluomeng/p/4760220.html
Copyright © 2020-2023  润新知