• 一文说通C#中的异步编程


    天天写,不一定就明白。

    又及,前两天看了一个关于同步方法中调用异步方法的文章,里面有些概念不太正确,所以整理了这个文章。

    一、同步和异步。

    先说同步。

    同步概念大家都很熟悉。在异步概念出来之前,我们的代码都是按同步的方式写的。简单来说,就是程序严格按照代码的逻辑次序,一行一行执行。

    看一段代码:

    public static void Main(string[] args)
    {
        Console.WriteLine("Syc proccess - start");

        Console.WriteLine("Syc proccess - enter Func1");
        func1();
        Console.WriteLine("Syc proccess - out Func1");

        Console.WriteLine("Syc proccess - enter Func2");
        func2();
        Console.WriteLine("Syc proccess - out Func2");

        Console.WriteLine("Syc proccess - enter Func3");
        func3();
        Console.WriteLine("Syc proccess - out Func3");

        Console.WriteLine("Syc proccess - done");
    }

    private static void func1()
    {
        Console.WriteLine("Func1 proccess - start");
        Thread.Sleep(1000);
        Console.WriteLine("Func1 proccess - end");
    }

    private static void func2()
    {
        Console.WriteLine("Func2 proccess - start");
        Thread.Sleep(3000);
        Console.WriteLine("Func2 proccess - end");
    }

    private static void func3()
    {
        Console.WriteLine("Func3 proccess - start");
        Thread.Sleep(5000);
        Console.WriteLine("Func3 proccess - end");
    }

    这是一段简单的通常意义上的代码,程序按代码的次序同步执行,看结果:

    Syc proccess - start
    Syc proccess - enter Func1
    Func1 proccess - start
    Func1 proccess - end
    Syc proccess - out Func1
    Syc proccess - enter Func2
    Func2 proccess - start
    Func2 proccess - end
    Syc proccess - out Func2
    Syc proccess - enter Func3
    Func3 proccess - start
    Func3 proccess - end
    Syc proccess - out Func3
    Syc proccess - done

    没有任何意外。

        为了防止不提供原网址的转载,特在这里加上原文链接:https://www.cnblogs.com/tiger-wang/p/13357981.html

    那异步呢?

    异步,来自于对同步处理的改良和优化。

    应用中,经常会有对于文件或网络、数据库的IO操作。这些操作因为IO软硬件的原因,需要消耗很多时间,但通常情况下CPU计算量并不大。在同步的代码中,这个过程会被阻塞。直白的说法就是这一行代码没执行完成,程序就得等着,等完成后再执行下一行代码,而这个等待的时间中,CPU资源就被浪费了,闲着了,什么也没做。(当然,操作系统会调度CPU干别的,这儿不抬杠。)

    异步编程模型和规范因此出现了,通过某种机制,让程序在等着IO的过程中,继续做点别的事,等IO的过程完成了,再回来处理IO的内容。这样CPU也没闲着,在等IO的过程中多做了点事。反映到用户端,就感觉程序更快了,用时更短了。

    下面重点说一下异步编程相关的内容。

    二、异步编程

    C#中,异步编程,一个核心,两个关键字。

    一个核心是指TaskTask<T>对象,而两个关键字,就是asyncawait

    从各种渠道给出的异步编程,都是下面的方式:

    async Task function()
    {
      /* your code here */
    }

    然后调用的方式:

    await function();

    是这样的吗?嗯,图样图森破~~~

    我们来看代码:

    static async Task Main(string[] args)
    {
        Console.WriteLine("Async proccess - start");

        Console.WriteLine("Async proccess - enter Func1");
        await func1();
        Console.WriteLine("Async proccess - out Func1");

        Console.WriteLine("Async proccess - enter Func2");
        await func2();
        Console.WriteLine("Async proccess - out Func2");

        Console.WriteLine("Async proccess - enter Func3");
        await func3();
        Console.WriteLine("Async proccess - out Func3");

        Console.WriteLine("Async proccess - done");

        Console.WriteLine("Main proccess - done");
    }

    private static async Task func1()
    {
        Console.WriteLine("Func1 proccess - start");
        Thread.Sleep(1000);
        Console.WriteLine("Func1 proccess - end");
    }

    private static async Task func2()
    {
        Console.WriteLine("Func2 proccess - start");
        Thread.Sleep(3000);
        Console.WriteLine("Func2 proccess - end");
    }

    private static async Task func3()
    {
        Console.WriteLine("Func3 proccess - start");
        Thread.Sleep(5000);
        Console.WriteLine("Func3 proccess - end");
    }

    跑一下结果:

    Async proccess - start
    Async proccess - enter Func1
    Func1 proccess - start
    Func1 proccess - end
    Async proccess - out Func1
    Async proccess - enter Func2
    Func2 proccess - start
    Func2 proccess - end
    Async proccess - out Func2
    Async proccess - enter Func3
    Func3 proccess - start
    Func3 proccess - end
    Async proccess - out Func3
    Async proccess - done
    Main proccess - done

    咦?这个好像跟同步代码的执行结果没什么区别啊?

    嗯,完全正确。上面这个代码,真的是同步执行的。

    这是异步编程的第一个容易错误的理解:asyncawait的配对。

    三、async和await的配对

    在异步编程的规范中,async修饰的方法,仅仅表示这个方法在内部有可能采用异步的方式执行,CPU在执行这个方法时,会放到一个新的线程中执行。

    那这个方法,最终是否采用异步执行,不决定于是否用await方式调用这个方法,而决定于这个方法内部,是否有await方式的调用。

    看代码,很容易理解:

    private static async Task func1()
    {
        Console.WriteLine("Func1 proccess - start");
        Thread.Sleep(1000);
        Console.WriteLine("Func1 proccess - end");
    }

    这个方法,因为方法内部没有await调用,所以这个方法永远会以同步方式执行,不管你调用这个方法时,有没有await

    而下面这个代码:

    private static async Task func1()
    {
        Console.WriteLine("Func1 proccess - start");
        await Task.Run(() => Thread.Sleep(1000));
        Console.WriteLine("Func1 proccess - end");
    }

    因为这个方法里有await调用,所以这个方法不管你以什么方式调用,有没有await,都是异步执行的。

    看代码:

    static async Task Main(string[] args)
    {
        Console.WriteLine("Async proccess - start");

        Console.WriteLine("Async proccess - enter Func1");
        func1();
        Console.WriteLine("Async proccess - out Func1");

        Console.WriteLine("Async proccess - enter Func2");
        func2();
        Console.WriteLine("Async proccess - out Func2");

        Console.WriteLine("Async proccess - enter Func3");
        func3();
        Console.WriteLine("Async proccess - out Func3");

        Console.WriteLine("Async proccess - done");

        Console.WriteLine("Main proccess - done");

        Console.ReadKey();
    }

    private static async Task func1()
    {
        Console.WriteLine("Func1 proccess - start");
        await Task.Run(() => Thread.Sleep(1000));
        Console.WriteLine("Func1 proccess - end");
    }

    private static async Task func2()
    {
        Console.WriteLine("Func2 proccess - start");
        await Task.Run(() => Thread.Sleep(3000));
        Console.WriteLine("Func2 proccess - end");
    }

    private static async Task func3()
    {
        Console.WriteLine("Func3 proccess - start");
        await Task.Run(() => Thread.Sleep(5000));
        Console.WriteLine("Func3 proccess - end");
    }

    输出结果:

    Async proccess - start
    Async proccess - enter Func1
    Func1 proccess - start
    Async proccess - out Func1
    Async proccess - enter Func2
    Func2 proccess - start
    Async proccess - out Func2
    Async proccess - enter Func3
    Func3 proccess - start
    Async proccess - out Func3
    Async proccess - done
    Main proccess - done
    Func1 proccess - end
    Func2 proccess - end
    Func3 proccess - end

    结果中,在长时间运行Thread.Sleep的时候,跳出去往下执行了,是异步。

    又有问题来了:不是说异步调用要用await吗?

    我们把await加到调用方法的前边,试一下:

    static async Task Main(string[] args)
    {
        Console.WriteLine("Async proccess - start");

        Console.WriteLine("Async proccess - enter Func1");
        await func1();
        Console.WriteLine("Async proccess - out Func1");

        Console.WriteLine("Async proccess - enter Func2");
        await func2();
        Console.WriteLine("Async proccess - out Func2");

        Console.WriteLine("Async proccess - enter Func3");
        await func3();
        Console.WriteLine("Async proccess - out Func3");

        Console.WriteLine("Async proccess - done");

        Console.WriteLine("Main proccess - done");

        Console.ReadKey();
    }

    跑一下结果:

    Async proccess - start
    Async proccess - enter Func1
    Func1 proccess - start
    Func1 proccess - end
    Async proccess - out Func1
    Async proccess - enter Func2
    Func2 proccess - start
    Func2 proccess - end
    Async proccess - out Func2
    Async proccess - enter Func3
    Func3 proccess - start
    Func3 proccess - end
    Async proccess - out Func3
    Async proccess - done
    Main proccess - done

    嗯?怎么又像是同步了?

    对,这是第二个容易错误的理解:await是什么意思?

    四、await是什么意思

    提到await,就得先说说Wait

    字面意思,Wait就是等待。

    前边说了,异步有一个核心,是Task。而Task有一个方法,就是Wait,写法是Task.Wait()。所以,很多人把这个Waitawait混为一谈,这是错的

    这个问题来自于Task。C#里,Task不是专为异步准备的,它表达的是一个线程,是工作在线程池里的一个线程。异步是线程的一种应用,多线程也是线程的一种应用。Wait,以及StatusIsCanceledIsCompletedIsFaulted等等,是给多线程准备的方法,跟异步没有半毛钱关系。当然你非要在异步中使用多线程的Wait或其它,从代码编译层面不会出错,但程序会。

    尤其,Task.Wait()是一个同步方法,用于多线程中阻塞等待。

    在那个「同步方法中调用异步方法」的文章中,用Task.Wait()来实现同步方法中调用异步方法,这个用法本身就是错误的。 异步不是多线程,而且在多线程中,多个Task.Wait()使用也会死锁,也有解决和避免死锁的一整套方式。

    再说一遍:Task.Wait()是一个同步方法,用于多线程中阻塞等待,不是实现同步方法中调用异步方法的实现方式。

    说回await。字面意思,也好像是等待。是真的吗?

    并不是,await不完全是等待的意思。

    在异步中,await表达的意思是:当前线程/方法中,await引导的方法出结果前,跳出当前线程/方法,从调用当前线程/方法的位置,去执行其它可能执行的线程/方法,并在引导的方法出结果后,把运行点拉回到当前位置继续执行;直到遇到下一个await,或线程/方法完成返回,跳回去刚才外部最后执行的位置继续执行。

    有点绕,还是看代码:

      static async Task Main(string[] args)
      
    {
    1     Console.WriteLine("Async proccess - start");

    2     Console.WriteLine("Async proccess - enter Func1");
    3     func1();
    4     Console.WriteLine("Async proccess - out Func1");

    5     Console.WriteLine("Async proccess - done");

    6         Thread.Sleep(2000);

    7     Console.WriteLine("Main proccess - done");

    8    Console.ReadKey();
      }

      private static async Task func1()
      
    {
    9     Console.WriteLine("Func1 proccess - start");
    10    await Task.Run(() => Thread.Sleep(1000));
    11    Console.WriteLine("Func1 proccess - end");
      }

    这个代码,执行时是这样的:顺序执行1、2、3,进到func1,执行9、10,到10时,有await,所以跳出,执行4、5、6。而6是一个长时等待,在等待的过程中,func1的10运行完成,运行点跳回10,执行11并结束方法,再回到6等待,结束等待后继续执行7、8结束。

    我们看一下结果:

    Async proccess - start
    Async proccess - enter Func1
    Func1 proccess - start
    Async proccess - out Func1
    Async proccess - done
    Func1 proccess - end
    Main proccess - done

    映证了这样的次序。

    在这个例子中,await在控制异步的执行次序。那为什么要用等待这么个词呢?是因为await确实有等待结果的含义。

    这是await的第二层意思。

    五、await的第二层意思:等待拿到结果

    await确实有等待的含义。等什么?等异步的运行结果。

    看代码:

    static async Task Main(string[] args)
    {
        Console.WriteLine("Async proccess - start");

        Console.WriteLine("Async proccess - enter Func1");
        Task<int> f = func1();
        Console.WriteLine("Async proccess - out Func1");

        Console.WriteLine("Async proccess - done");

        int result = await f;

        Console.WriteLine("Main proccess - done");

        Console.ReadKey();
    }

    private static async Task<int> func1()
    {
        Console.WriteLine("Func1 proccess - start");
        await Task.Run(() => Thread.Sleep(1000));
        Console.WriteLine("Func1 proccess - end");

        return 5;
    }

    比较一下这段代码和上一节的代码,很容易搞清楚执行过程。

    这个代码,完成了这样一个需求:我们需要使用func1方法的返回值。我们可以提前去执行这个方法,而不急于拿到方法的返回值,直到我们需要使用时,再用await去获取到这个返回值去使用。

    这才是异步对于我们真正的用处。对于一些耗时的IO或类似的操作,我们可以提前调用,让程序可以利用执行过程中的空闲时间来完成这个操作。等到我们需要这个操作的结果用于后续的执行时,我们await这个结果。这时候,如果await的方法已经执行完成,那我们可以马上得到结果;如果没有完成,则程序将继续执行这个方法直到得到结果。

    六、同步方法中调用异步

    正确的方法只有一个:

    func1().GetAwaiter().GetResult();

    这其实就是await的一个变形。

    (全文完)

     


    微信公众号:老王Plus

    扫描二维码,关注个人公众号,可以第一时间得到最新的个人文章和内容推送

    本文版权归作者所有,转载请保留此声明和原文链接

  • 相关阅读:
    vim编辑参数
    hive的元数据存储在mysql后,报错的解决方法!
    hadoop添加删除节点
    Android画图Path的使用
    android中path的arcTo方法的使用 .
    StrongReference、SoftReference、WeakReference、PhantomReference .
    为Android应用增加渠道信息 自动化不同渠道的打包过程
    C#知识点
    CSS判断浏览器
    调用外部程序
  • 原文地址:https://www.cnblogs.com/tiger-wang/p/13357981.html
Copyright © 2020-2023  润新知