批判“await使用中的阻塞和并发”——对asyc/await这对基友的误会和更正

  写第一篇《await使用中的阻塞和并发》的时候还自信满满,觉得写的真不错,结果漏洞百出……

  更正第二篇《await使用中的阻塞和并发(二)》的时候觉得这回不会再错了……

  结果我正在写第三篇,而且连篇名都不敢延用了……

  首先完善第二篇对Foreach(Action<T>)的拆解,用很厉害的小兄弟geelaw的话说就是“是用另一个方法返回λ表达式创建的委托,并未把λ表达式换成方法。”惭愧啊,在小兄弟的指点下,修改代码如下,在Foreach(Action)这个话题上算是圆满了:

        public void AwaitFuncTaskListNoLambda()
        {
            var funcList = new List<Func<Task>>()
            {
                Delay3000Async,
                Delay2000Async,
                Delay1000Async
            };
 
            //funcList.ForEach(async _ => await _());
            //funcList.ForEach(AwaitAction());
            //foreach (var func in funcList)
            //{
            //    AwaitAction()(func);
            //}
 
            Action<Func<Task>> L = this.Xxx;
            foreach (var func in funcList)
            {
                L(func);
            }
        }
        
        async void Xxx(Func<Task> ft)
        {
            await ft();
        }

        public Action<Func<Task>> AwaitAction()
        {
            return async _ => await _();
        }
    

  接下来的文字是本篇的重点,来自redjackwang菊苣的指点,他指出了我第二篇中仍存在的误区和错误。下面让我们来领略一下大牛的风采。首先从上篇我给出的结论入手:

  1. async用于异步,可以优美的替代Thread、BackgroundWorker和Task.Run等写法。
  2. await用于等待。一定是在你主动希望阻塞并等待返回结果时才使用。
  3. 在async方法里,Task在创建时就开始运行了。

  第一点需要指出这里的替代仅仅是写法的变化,async方法内部具体实现可能仍使用Thread等多线程技术来实现。这里需要明确async/await仅是一个异步的写法,而Thread\BackgroundWorker和Task是多线程。

  关于第二点,有两位菊苣分别提出异议。redjackwang的原话是“await并不会阻塞任何线程。await只是把当前方法状态保存并立即返回,不管是主线程还是后台线程都不会阻塞,而是在完成时call一个回调。”胖胖的韦恩卑鄙是这么说的:“说await是主动阻塞问题很大。那叫做响应式休眠?

  至于第三点,那是MSDN坑我,redjackwang大人竟然从MSDN上找到了更详细的答案来证明我给出的链接是错误的。在此一定要共享出来给各位,不要被我上篇错误的结论误导了,正确的表述如下:

Tasks created by its public constructors are referred to as “cold” tasks, in that they begin their life cycle in the non-scheduled TaskStatus.Created state, and it’s not until Start is called on these instances that they progress to being scheduled.  All other tasks begin their life cycle in a “hot” state, meaning that their asynchronous execution has already been initiated and their TaskStatus is an enumeration value other than Created. 

All tasks returned from TAP methods must be “hot.”  If a TAP method internally uses a Task’s constructor to instantiate the task to be returned, the TAP method must call Start on the Task object prior to returning it. Consumers of a TAP method may safely assume that the returned task is “hot,” and should not attempt to call Start on any Task returned from a TAP method.  Calling Start on a “hot” task will result in an InvalidOperationException (this check is handled automatically by the Task class). 

  为此redjackwang还给出了证明的例子,在这个例子中,如果注释掉start方法,Task是不会自动运行的。结论是Task如果是通过构造函数创建的,状态是cold的,不会自动运行,而前一篇都是通过返回Task的方法创建出来的,状态是hot,所以自动运行了,和在不在async方法中没有关系。

private async void Button_Click(object sender, RoutedEventArgs e)
{
    var v = await Foo();
    this.Title = v.ToString();
}
 
private async Task<long> Foo()
{
    var t = new Task<long>(() =>
    {
        long z = 0;
        for (int i = 0; i < 100000; i++)
        {
            z += i;
        }
        return z;
    });
    //t.Start();
    return await t;
}

  本篇应不是最终的结果,后面如有进一步的发现仍会更新下去,批判的暴风雨还在继续……

 

 

 

posted @ 2014-02-16 16:03  楼上那个蜀黍  阅读(3522)  评论(6编辑  收藏  举报