• async和await的使用总结 ~ 竟然一直用错了c#中的async和await的使用。。


    对于c#中的async和await的使用,没想到我一直竟然都有一个错误。。

    。。还是总结太少,这里记录下。

    这里以做早餐为例

    流程如下:

    1. 倒一杯咖啡。
    2. 加热平底锅,然后煎两个鸡蛋。
    3. 煎三片培根。
    4. 烤两片面包。
    5. 在烤面包上加黄油和果酱。
    6. 倒一杯橙汁。

    当使用同步方式实现时,代码是这样的:

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace AsyncBreakfast
    {
        class Program
        {
            static void Main(string[] args)
            {
                var sw = new Stopwatch();
                sw.Start();
                Coffee cup = PourCoffee();
                Console.WriteLine("coffee is ready");
    
                Egg eggs = FryEggs(2);
                Console.WriteLine("eggs are ready");
    
                Bacon bacon = FryBacon(3);
                Console.WriteLine("bacon is ready");
    
                Toast toast = ToastBread(2);
                ApplyButter(toast);
                ApplyJam(toast);
                Console.WriteLine("toast is ready");
    
                Juice oj = PourOJ();
                Console.WriteLine("oj is ready");
                Console.WriteLine("Breakfast is ready!");
    
                Console.WriteLine($"totol time:{sw.ElapsedMilliseconds/1000}");
                Console.ReadKey();
            }
    
            private static Juice PourOJ()
            {
                Console.WriteLine("Pouring orange juice");
                return new Juice();
            }
    
            private static void ApplyJam(Toast toast) =>
                Console.WriteLine("Putting jam on the toast");
    
            private static void ApplyButter(Toast toast) =>
                Console.WriteLine("Putting butter on the toast");
    
            private static Toast ToastBread(int slices)
            {
                for (int slice = 0; slice < slices; slice++)
                {
                    Console.WriteLine("Putting a slice of bread in the toaster");
                }
                Console.WriteLine("Start toasting...");
                Task.Delay(3000).Wait();
                Console.WriteLine("Remove toast from toaster");
    
                return new Toast();
            }
    
            private static Bacon FryBacon(int slices)
            {
                Console.WriteLine($"putting {slices} slices of bacon in the pan");
                Console.WriteLine("cooking first side of bacon...");
                Task.Delay(3000).Wait();
                for (int slice = 0; slice < slices; slice++)
                {
                    Console.WriteLine("flipping a slice of bacon");
                }
                Console.WriteLine("cooking the second side of bacon...");
                Task.Delay(3000).Wait();
                Console.WriteLine("Put bacon on plate");
    
                return new Bacon();
            }
    
            private static Egg FryEggs(int howMany)
            {
                Console.WriteLine("Warming the egg pan...");
                Task.Delay(3000).Wait();
                Console.WriteLine($"cracking {howMany} eggs");
                Console.WriteLine("cooking the eggs ...");
                Task.Delay(3000).Wait();
                Console.WriteLine("Put eggs on plate");
    
                return new Egg();
            }
    
            private static Coffee PourCoffee()
            {
                Console.WriteLine("Pouring coffee");
                return new Coffee();
            }
        }
        class Coffee { }
        class Egg { }
        class Bacon { }
        class Toast { }
        class Juice { }
    }

    运行效果如下:

    或表示为这样

    同步准备的早餐大约花费了 30 分钟,因为总耗时是每个任务耗时的总和。这里的total time只是用来表示记录下程序运行的时间。

    而我以前写的异步代码是这样的:

    using System;
    using System.Diagnostics;
    using System.Threading.Tasks;
    
    namespace AsyncBreakfast
    {
        class Program
        {
            static async void Main(string[] args)
            {
                var sw = new Stopwatch();
                sw.Start();
                Coffee cup = PourCoffee();
                Console.WriteLine("coffee is ready");
    
                Egg eggs = await FryEggsAsync(2);
                Console.WriteLine("eggs are ready");
    
                Bacon bacon = await FryBaconAsync(3);
                Console.WriteLine("bacon is ready");
    
                Toast toast = await ToastBreadAsync(2);
                ApplyButter(toast);
                ApplyJam(toast);
                Console.WriteLine("toast is ready");
    
                Juice oj = PourOJ();
                Console.WriteLine("oj is ready");
                Console.WriteLine("Breakfast is ready!");
    
                Console.WriteLine($"totol time:{sw.ElapsedMilliseconds/1000}");
                Console.ReadKey();
            }
    
            static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
            {
                var toast = await ToastBreadAsync(number);
                ApplyButter(toast);
                ApplyJam(toast);
    
                return toast;
            }
    
            private static Juice PourOJ()
            {
                Console.WriteLine("Pouring orange juice");
                return new Juice();
            }
    
            private static void ApplyJam(Toast toast) =>
                Console.WriteLine("Putting jam on the toast");
    
            private static void ApplyButter(Toast toast) =>
                Console.WriteLine("Putting butter on the toast");
    
            private static async Task<Toast> ToastBreadAsync(int slices)
            {
                for (int slice = 0; slice < slices; slice++)
                {
                    Console.WriteLine("Putting a slice of bread in the toaster");
                }
                Console.WriteLine("Start toasting...");
                await Task.Delay(3000);
                Console.WriteLine("Remove toast from toaster");
    
                return new Toast();
            }
    
            private static async Task<Bacon> FryBaconAsync(int slices)
            {
                Console.WriteLine($"putting {slices} slices of bacon in the pan");
                Console.WriteLine("cooking first side of bacon...");
                await Task.Delay(3000);
                for (int slice = 0; slice < slices; slice++)
                {
                    Console.WriteLine("flipping a slice of bacon");
                }
                Console.WriteLine("cooking the second side of bacon...");
                await Task.Delay(3000);
                Console.WriteLine("Put bacon on plate");
    
                return new Bacon();
            }
    
            private static async Task<Egg> FryEggsAsync(int howMany)
            {
                Console.WriteLine("Warming the egg pan...");
                await Task.Delay(3000);
                Console.WriteLine($"cracking {howMany} eggs");
                Console.WriteLine("cooking the eggs ...");
                await Task.Delay(3000);
                Console.WriteLine("Put eggs on plate");
    
                return new Egg();
            }
    
            private static Coffee PourCoffee()
            {
                Console.WriteLine("Pouring coffee");
                return new Coffee();
            }
    
        }
        class Coffee { }
        class Egg { }
        class Bacon { }
        class Toast { }
        class Juice { }
    }

    效果如下:

     可以看出,这样编写的异步和最初同步版本的总共的耗时大致相同。

    这是因为这段代码还没有利用异步编程的某些关键功能。

    即上面的异步代码的使用在这里是不准确的。

    可以看出,这段代码里面的打印输出与同步是一样的。

    这是因为:在煎鸡蛋或培根时,此代码虽然不会阻塞,但是此代码也不会启动任何其他任务。

    就造成了异步煎鸡蛋的操作完成后,才会开始培根制作。

    但是,对于这里而言,我不希望每个任务都按顺序依次执行。

    最好是首先启动每个组件任务,然后再等待之前任务的完成。

    例如:首先启动鸡蛋和培根。

    同时启动任务

    在很多方案中,你可能都希望立即启动若干独立的任务。然后,在每个任务完成时,你可以继续

    进行已经准备的其他工作。

    就像这里同时启动煎鸡蛋,培根和烤面包。

    我们这里对早餐代码做些更改。

    正确的做法

    第一步是存储任务以便在这些任务启动时进行操作,而不是等待:

    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");
    
    Task<Egg> eggsTask = FryEggsAsync(2);
    Egg eggs = await eggsTask;
    Console.WriteLine("eggs are ready");
    
    Task<Bacon> baconTask = FryBaconAsync(3);
    Bacon bacon = await baconTask;
    Console.WriteLine("bacon is ready");
    
    Task<Toast> toastTask = ToastBreadAsync(2);
    Toast toast = await toastTask;
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");
    
    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    Console.WriteLine("Breakfast is ready!");

    接下来,可以在提供早餐之前将用于处理培根和鸡蛋的await语句移动到此方法的末尾:

    Coffee cup = PourCoffee();
    Console.WriteLine("coffee is ready");
    
    Task<Egg> eggsTask = FryEggsAsync(2);
    Task<Bacon> baconTask = FryBaconAsync(3);
    Task<Toast> toastTask = ToastBreadAsync(2);
    
    Toast toast = await toastTask;
    ApplyButter(toast);
    ApplyJam(toast);
    Console.WriteLine("toast is ready");
    Juice oj = PourOJ();
    Console.WriteLine("oj is ready");
    
    Egg eggs = await eggsTask;
    Console.WriteLine("eggs are ready");
    Bacon bacon = await baconTask;
    Console.WriteLine("bacon is ready");
    
    Console.WriteLine("Breakfast is ready!");

    运行效果如下:

     或者

     可以看出,这里一次启动了所有的异步任务。而你仅在需要结果时,才会等待每项任务

    这里异步准备的造成大约花费20分钟,这是因为一些任务可以并发进行。

    而对于直接  Egg eggs = await FryEggsAsync(2); 的方式,适用于你只需要等待这一个异步操作结果,不需要进行其他操作的时候。

    与任务组合

    吐司操作由异步操作(烤面包)和同步操作(添加黄油和果酱)组成。

    这里涉及到一个重要概念:

    异步操作后跟同步操作的这种组合也是一个异步操作。

    也就是说,如果操作的任何部分是异步的,整个操作就是异步的。

    代码如下:

    static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
    {
        var toast = await ToastBreadAsync(number);
        ApplyButter(toast);
        ApplyJam(toast);
    
        return toast;
    }

    所有,主要代码块现在变为:

    static async Task Main(string[] args)
    {
        Coffee cup = PourCoffee();
        Console.WriteLine("coffee is ready");
        
        var eggsTask = FryEggsAsync(2);
        var baconTask = FryBaconAsync(3);
        var toastTask = MakeToastWithButterAndJamAsync(2);
    
        var eggs = await eggsTask;
        Console.WriteLine("eggs are ready");
    
        var bacon = await baconTask;
        Console.WriteLine("bacon is ready");
    
        var toast = await toastTask;
        Console.WriteLine("toast is ready");
    
        Juice oj = PourOJ();
        Console.WriteLine("oj is ready");
        Console.WriteLine("Breakfast is ready!");
    }

    高效的等待任务

    可以通过使用Task类的方法改进上述代码末尾一系列await语句。

    WhenAll 是其中的一个api , 它将返回一个其参数列表中的所有任务都已完成时猜完成的Task,

    代码如下

    await Task.WhenAll(eggsTask, baconTask, toastTask);
    Console.WriteLine("eggs are ready");
    Console.WriteLine("bacon is ready");
    Console.WriteLine("toast is ready");
    Console.WriteLine("Breakfast is ready!");

    另一种选择是 WhenAny, 它将返回一个,当其参数完成时猜完成的 Task<Task>。

    var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
    while (breakfastTasks.Count > 0)
    {
        Task finishedTask = await Task.WhenAny(breakfastTasks);
        if (finishedTask == eggsTask)
        {
            Console.WriteLine("eggs are ready");
        }
        else if (finishedTask == baconTask)
        {
            Console.WriteLine("bacon is ready");
        }
        else if (finishedTask == toastTask)
        {
            Console.WriteLine("toast is ready");
        }
        breakfastTasks.Remove(finishedTask);
    }

    处理已完成任务的结果之后,可以从传递给 WhenAny 的任务列表中删除此已完成的任务。

     进行这些更改后,代码的最终版本将如下所示:

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    
    namespace AsyncBreakfast
    {
        class Program
        {
            static async Task Main(string[] args)
            {
                Coffee cup = PourCoffee();
                Console.WriteLine("coffee is ready");
    
                var eggsTask = FryEggsAsync(2);
                var baconTask = FryBaconAsync(3);
                var toastTask = MakeToastWithButterAndJamAsync(2);
    
                var breakfastTasks = new List<Task> { eggsTask, baconTask, toastTask };
                while (breakfastTasks.Count > 0)
                {
                    Task finishedTask = await Task.WhenAny(breakfastTasks);
                    if (finishedTask == eggsTask)
                    {
                        Console.WriteLine("eggs are ready");
                    }
                    else if (finishedTask == baconTask)
                    {
                        Console.WriteLine("bacon is ready");
                    }
                    else if (finishedTask == toastTask)
                    {
                        Console.WriteLine("toast is ready");
                    }
                    breakfastTasks.Remove(finishedTask);
                }
    
                Juice oj = PourOJ();
                Console.WriteLine("oj is ready");
                Console.WriteLine("Breakfast is ready!");
            }
    
            static async Task<Toast> MakeToastWithButterAndJamAsync(int number)
            {
                var toast = await ToastBreadAsync(number);
                ApplyButter(toast);
                ApplyJam(toast);
    
                return toast;
            }
    
            private static Juice PourOJ()
            {
                Console.WriteLine("Pouring orange juice");
                return new Juice();
            }
    
            private static void ApplyJam(Toast toast) =>
                Console.WriteLine("Putting jam on the toast");
    
            private static void ApplyButter(Toast toast) =>
                Console.WriteLine("Putting butter on the toast");
    
            private static async Task<Toast> ToastBreadAsync(int slices)
            {
                for (int slice = 0; slice < slices; slice++)
                {
                    Console.WriteLine("Putting a slice of bread in the toaster");
                }
                Console.WriteLine("Start toasting...");
                await Task.Delay(3000);
                Console.WriteLine("Remove toast from toaster");
    
                return new Toast();
            }
    
            private static async Task<Bacon> FryBaconAsync(int slices)
            {
                Console.WriteLine($"putting {slices} slices of bacon in the pan");
                Console.WriteLine("cooking first side of bacon...");
                await Task.Delay(3000);
                for (int slice = 0; slice < slices; slice++)
                {
                    Console.WriteLine("flipping a slice of bacon");
                }
                Console.WriteLine("cooking the second side of bacon...");
                await Task.Delay(3000);
                Console.WriteLine("Put bacon on plate");
    
                return new Bacon();
            }
    
            private static async Task<Egg> FryEggsAsync(int howMany)
            {
                Console.WriteLine("Warming the egg pan...");
                await Task.Delay(3000);
                Console.WriteLine($"cracking {howMany} eggs");
                Console.WriteLine("cooking the eggs ...");
                await Task.Delay(3000);
                Console.WriteLine("Put eggs on plate");
                
                return new Egg();
            }
    
            private static Coffee PourCoffee()
            {
                Console.WriteLine("Pouring coffee");
                return new Coffee();
            }
        }
    }

    效果如下:

    或者

     这种异步的代码实现最终大约花费15分钟,因为一些任务能同时运行,

    并且该代码能够同时监视多个任务,只在需要时才执行操作。

    总结:

    async 和 await的功能最好能做到:

    尽可能启动任务,不要在等待任务完成时造成阻塞。

    即可以先把任务存储到task,然后在后面需要用的时候,调用await task()方法。

    参考网址:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/

  • 相关阅读:
    类的定义
    面向对象与面向过程介绍
    跨目录导入模块
    正则表达式re模块
    常用工具包(模块)
    生成器generator
    闭包
    命名空间name space
    函数的递归
    POJ1062
  • 原文地址:https://www.cnblogs.com/Vincent-yuan/p/13417356.html
Copyright © 2020-2023  润新知