zoukankan      html  css  js  c++  java
  • async/await的特殊的地方

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

    不过void的最好不要用,如果说方法没有返回值用Task,有返回值用Task<TResult>;

    先看两段代码:

    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; }

    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>对象);

    然后再看另外一种写法

    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不会阻塞调用者;

  • 相关阅读:
    nginx-1.8.1的安装
    ElasticSearch 在3节点集群的启动
    The type java.lang.CharSequence cannot be resolved. It is indirectly referenced from required .class files
    sqoop导入导出对mysql再带数据库test能跑通用户自己建立的数据库则不行
    LeetCode 501. Find Mode in Binary Search Tree (找到二叉搜索树的众数)
    LeetCode 437. Path Sum III (路径之和之三)
    LeetCode 404. Sum of Left Leaves (左子叶之和)
    LeetCode 257. Binary Tree Paths (二叉树路径)
    LeetCode Questions List (LeetCode 问题列表)- Java Solutions
    LeetCode 561. Array Partition I (数组分隔之一)
  • 原文地址:https://www.cnblogs.com/silentdoer/p/10277345.html
Copyright © 2011-2022 走看看