async/await的特殊的地方

一:async若是是用于方法声明里,那么要求这个方法的返回值必须是Task、Task<TResult>、void这三种,并且await出现的地方要求其所在的方法必须是async修饰的方法;闭包

不过void的最好不要用,若是说方法没有返回值用Task,有返回值用Task<TResult>;异步

先看两段代码:async

测试

static void Main(string[] args)
        {
            var task = FooAsync();
            // Result的get方法会阻塞当前线程直到task执行完毕
            Console.WriteLine(task.Result);
            Console.WriteLine("Hello World!");
        }

        static async Task<int> FooAsync()
        {
       // await task对象能够直接获取task对象执行完毕的返回值,所以await会将Task.Run(..)做为同步块; int result = await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕"); return 8; }); return result; }

pwa

static void Main(string[] args)
        {
            var task = FooAsync();
            // Result的get方法会阻塞当前线程直到task执行完毕
            Console.WriteLine(task.Result);
            Console.WriteLine("Hello World!");
        }

     // TODO 注意,这个地方去掉了async和里面的await static Task<int> FooAsync() { // 这时候不能用int做为变量类型了 Task<int> result = Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕"); return 8; }); return result; }

  对于上面的async和await的写法,那么async和await关键字其实能够说一点用处也没有,直接经过Task就能达到彻底如出一辙的效果;线程

接着来看另外一种写法:对象

static void Main(string[] args)
        {
            var task = FooAsync();
            // Result的get方法会阻塞当前线程直到task执行完毕
            Console.WriteLine(task.Result);
            Console.WriteLine("Hello World!");
        }

        /**
         * 注意,Task是后台任务(由后台线程执行),而Java的Task,或者说Java默认的线程建立工厂建立的线程是前台线程
         */
        static async Task<string> FooAsync()
        {
            // 这时候不能用int做为变量类型了
            Task<int> result = Task.Run(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕");
                return 8;
            });
            int resultValue = await result;
            return "333N" + resultValue;  // TODO 注意这里多了转换结果的步骤,用async+await写这种需求很方便,若是只用Task那么就得把Task.Run(...)提高到外部调用的地方才能方便的实现这个功能;
        }

  这种写法只用Task<TResult>就不容易实现了,至少写起来要多蛮多步骤(须要再手动写个Task<string>对象而后用lambda表达式将await出来的结果转换为string返回出来,而后返回这个new出来的Task<string>对象);blog

而后再看另一种写法get

static void Main(string[] args)
        {
            var watch1 = Stopwatch.StartNew();
            var watch2 = Stopwatch.StartNew();
            Console.WriteLine("Begin");
            var task = FooAsync();
            watch1.Stop();
          
            Console.WriteLine($"#耗时{watch1.ElapsedMilliseconds}");
            
            // Result的get方法会阻塞当前线程直到task执行完毕
            // 这个若是注释掉,那么FooAsync则只阻塞主线程2秒左右,若是不注释下面的代码,那么上面的FooAsync()会先阻塞主线程
            //Console.WriteLine(task.Result);
            watch2.Stop();
            
            Console.WriteLine($"耗时{watch2.ElapsedMilliseconds}");
            
            Console.WriteLine("End");
        }

        /**
         * 注意,Task是后台任务(由后台线程执行),而Java的Task,或者说Java默认的线程建立工厂建立的线程是前台线程(可配置)
         */
        static async Task<int> FooAsync()
        {
            Thread.Sleep(2000);  // 这一个会阻塞调用者
            Console.WriteLine($"{nameof(FooAsync)}方法先同步耗时2秒");
            // 这时候不能用int做为变量类型了
            var result = Task.Run(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕");
                return 8;
            });
            Thread.Sleep(2000);  // 重要,这一块不必定会阻塞调用方(await放在Task.Run那里)
            Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗时2秒");
            // 注意,若是这个await放到上面的Task.Run(..)那里会令这一块的逻辑很奇怪,并且貌似执行是有问题的,都不知道它执行完毕没有
            return await result;
        }

  其实仔细一比较,async和await是能够不须要的,用Task<TResult>就彻底能够实现,只不过有部分写法会稍微麻烦一点点而已;await task;就能够用task.Result来代替;编译器

async加await其实就是实现这样一个功能:

有async的方法,是在告诉编译器这个方法内部可能存在异步调用,如Task.Run(...);
而后内部对这个异步调用进行一个await(没有也行可是就失去了async和await带来的糖);
这个糖的最重要的提如今于外部调用这个方法时,如这个方法叫FooAsync(),调用过程为:
var task = FooAsync();
若是接下来的代码没有相似task.Wait()或task.Result这样的等待操做,那么FooAsync()里面的异步部分对于调用者也是异步的(包括await后面的所有代码对于调用者都是异步的,这点很重要,如var result = await Task.Run(...);Thread.Sleep(2000);这个Sleep不会阻塞调用者);
async/await实际上是一种相似模板类的技术,有async且内部有await的方法是一种具有不一样编译状况的方法(怎么编译要看怎么调用及使用具备async的方法,如调用者有task.Result是一种使用模式,没有task.Result或没有await task或task.Wait..之类的又是另外一种模式)
static void Main(string[] args)
        {
            // async/await实际上是一种相似模板类的技术,有async且内部有await的方法是一种具有不一样编译状况的方法;
            var watch1 = Stopwatch.StartNew();
            var watch2 = Stopwatch.StartNew();
            Console.WriteLine("Begin");
            var task = FooAsync();
            watch1.Stop();
          
            Console.WriteLine($"#耗时{watch1.ElapsedMilliseconds}");
            
            // Result的get方法会阻塞当前线程直到task执行完毕
            // 这个若是注释掉,那么FooAsync则只阻塞主线程2秒左右,若是不注释下面的代码,那么上面的FooAsync()会先阻塞主线程
            //Console.WriteLine(task.Result);
            watch2.Stop();
            
            Console.WriteLine($"耗时{watch2.ElapsedMilliseconds}");
            
            Console.WriteLine("End");
            // 这里能够经过输出End后马上按回车或等几秒后按回车进行测试;
            Console.ReadKey();
        }

        /**
         * 注意,Task是后台任务(由后台线程执行),而Java的Task,或者说Java默认的线程建立工厂建立的线程是前台线程(可配置)
      * 甚至编译器将FooAsync()方法编译成了FooAsync`1()和FooAsync`2()两个子方法,而后看调用者是否涉及到同步FooAsync方法结果的操做,有则编译器给调用方调用的是FooAsync`1(),没有则是用的FooAsync`2()方法 */ static async Task<int> FooAsync() { Thread.Sleep(2000); // 这一个会阻塞调用者 Console.WriteLine($"{nameof(FooAsync)}方法先同步耗时2秒"); // 注意,await以后的全部代码是否阻塞调用者取决于调用者怎么用这个方法,若是调用者是await FooAsync(),那么这里以后的全部代码都是同步的 // TODO 因此async和await和Task<TResult>相比多了一个相似开关同样的东西,可是这个开关是编译阶段肯定的,有点像模板类用的时候必须给出具体的类型,而这里是具体的用法; // TODO 编译时若是调用者有task.Result之类的wait操做,那么【编译器】就将FooAsync整个当成一个同步方法,若是没有相关的操做,编译器就将这部分及其后面的代码做为异步方法处理【应该是经过闭包造成一个新的Task对象】 // TODO 这里往下的代码,若是编译器发现调用方不须要同步,那么编译器底层是经过再开一个Task<int>将result和下面的Thread.Sleep(4000)等代码用闭包再包一层返回这个task对象; var result = await Task.Run(() => { Thread.Sleep(3000); Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕"); return 8; }); // TODO 注意,这一块是在上面的result执行完后才执行的,即上面先输出 FooAsync方法耗时3秒执行完毕 而后等待4秒后再输出 ###FooAsync方法先同步耗时4秒 Thread.Sleep(4000); // 重要,这一块不必定会阻塞调用方(await放在Task.Run那里),这一块是否阻塞调用者看调用者是否须要对返回的Task对象进行wait,且是编译时决定的; Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗时4秒"); // 注意,若是这个await放到上面的Task.Run(..)那里会令这一块的逻辑很奇怪,并且貌似执行是有问题的,都不知道它执行完毕没有 return result; }

  

来看进一步的测试:

static void Main(string[] args)
        {
            // async/await实际上是一种相似模板类的技术,有async且内部有await的方法是一种具有不一样编译状况的方法;
            var watch1 = Stopwatch.StartNew();
            var watch2 = Stopwatch.StartNew();
            Console.WriteLine($"Begin,threadId:{Thread.CurrentThread.ManagedThreadId}");
            var task = FooAsync();
            watch1.Stop();
          
            Console.WriteLine($"#耗时{watch1.ElapsedMilliseconds}");
            
            // Result的get方法会阻塞当前线程直到task执行完毕
            // 这个若是注释掉,那么FooAsync则只阻塞主线程2秒左右,若是不注释下面的代码,那么上面的FooAsync()会先阻塞主线程
            //Console.WriteLine(task.Result);  // TODO flagN
            watch2.Stop();
            
            Console.WriteLine($"耗时{watch2.ElapsedMilliseconds}");
            
            Console.WriteLine("End");
            // 这里能够经过输出End后马上按回车或等几秒后按回车进行测试;
            Console.ReadKey();
        }

        /**
         * 注意,Task是后台任务(由后台线程执行),而Java的Task,或者说Java默认的线程建立工厂建立的线程是前台线程(可配置)
         */
        static async Task<int> FooAsync()
        {
            Thread.Sleep(2000);  // 这一个会阻塞调用者
            Console.WriteLine($"{nameof(FooAsync)}方法先同步耗时2秒");
            // 注意,await以后的全部代码是否阻塞调用者取决于调用者怎么用这个方法,若是调用者是await FooAsync(),那么这里以后的全部代码都是同步的
            // TODO 因此async和await和Task<TResult>相比多了一个相似开关同样的东西,可是这个开关是编译阶段肯定的,有点像模板类用的时候必须给出具体的类型,而这里是具体的用法;
            // TODO 编译时若是调用者有task.Result之类的wait操做,那么【编译器】就将FooAsync整个当成一个同步方法,若是没有相关的操做,编译器就将这部分及其后面的代码做为异步方法处理【应该是经过闭包造成一个新的Task对象】
            // TODO 这里往下的代码,若是编译器发现调用方不须要同步,那么编译器底层是经过再开一个Task<int>将result和下面的Thread.Sleep(4000)等代码用闭包再包一层返回这个task对象;
            var result = await Task.Run(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕,threadId:{Thread.CurrentThread.ManagedThreadId}");
                return 8;
            });
            // TODO 注意,这一块是在上面的result执行完后才执行的,即上面先输出 FooAsync方法耗时3秒执行完毕 而后等待4秒后再输出 ###FooAsync方法先同步耗时4秒
            Thread.Sleep(4000);  // 重要,这一块不必定会阻塞调用方(await放在Task.Run那里),这一块是否阻塞调用者看调用者是否须要对返回的Task对象进行wait,且是编译时决定的;
            Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗时4秒,threadIdNNN:{Thread.CurrentThread.ManagedThreadId}");
            return result;
        }

  通过测试,flagN行不管是否注释,主线程的threadId和后面Task.Run及后面的threadIdNNN的线程id都是不一样的,可是后面两个的线程id是一致的,所以编译器底层不是生成了FooAsync`1和FooAsync`2两个方法,而是将await及后面的代码合并为

一块,即FooAsync其实最终是变成这样子的代码(不必定百分百就是这个样子,可是能够解释测试结果):

static async Task<int> FooAsync()
        {
            Thread.Sleep(2000);  // 这一个会阻塞调用者
            Console.WriteLine($"{nameof(FooAsync)}方法先同步耗时2秒");
            // 注意,await以后的全部代码是否阻塞调用者取决于调用者怎么用这个方法,若是调用者是await FooAsync(),那么这里以后的全部代码都是同步的
            // TODO 因此async和await和Task<TResult>相比多了一个相似开关同样的东西,可是这个开关是编译阶段肯定的,有点像模板类用的时候必须给出具体的类型,而这里是具体的用法;
            // TODO 编译时若是调用者有task.Result之类的wait操做,那么【编译器】就将FooAsync整个当成一个同步方法,若是没有相关的操做,编译器就将这部分及其后面的代码做为异步方法处理【应该是经过闭包造成一个新的Task对象】
            // TODO 这里往下的代码,若是编译器发现调用方不须要同步,那么编译器底层是经过再开一个Task<int>将result和下面的Thread.Sleep(4000)等代码用闭包再包一层返回这个task对象;
            var result = Task.Run(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine($"{nameof(FooAsync)}方法耗时3秒执行完毕,threadId:{Thread.CurrentThread.ManagedThreadId}");
                var tmp = 8;
                
                // TODO 注意,这一块是在上面的result执行完后才执行的,即上面先输出 FooAsync方法耗时3秒执行完毕 而后等待4秒后再输出 ###FooAsync方法先同步耗时4秒
                Thread.Sleep(4000);  // 重要,这一块不必定会阻塞调用方(await放在Task.Run那里),这一块是否阻塞调用者看调用者是否须要对返回的Task对象进行wait,且是编译时决定的;
                Console.WriteLine($"###{nameof(FooAsync)}方法先同步耗时4秒,threadIdNNN:{Thread.CurrentThread.ManagedThreadId}");
                
                return 8;
            });
            return result;
        }

  这个result不会阻塞调用者;

相关文章
相关标签/搜索