5.3C#异步编程完整示例

以下将用”如何烹饪早餐”示例来解释异步:

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

1.同步阻塞

using System;
using System.Threading.Tasks;

namespace AsyncBreakfast
{
    class Program
    {
        static void Main(string[] args)
        {
            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!");
        }

        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();
        }
    }
}
5.3C#异步编程完整示例

同步准备的早餐大约花费了 30 分钟,因为总耗时是每个任务耗时的总和。

一个厨师在烹饪第一份早餐的时候,不会同时准备第二份早餐。

此段代码不仅会阻塞方法的调用方,还会阻塞方法内的后续代码执行。

2.异步无阻塞

FryEggsAsyncFryBaconAsync 和 ToastBreadAsync 的方法主体都已更新,现会分别返回 Task<Egg>Task<Bacon> 和 Task<Toast>

static async Task Main(string[] args)
{
    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!");
}

因为异步无阻塞,一个厨师在烹饪第一份早餐的时候,会同时准备第二份早餐。

异步总运行时间和同步代码大致相同,虽然 await运算符将控制权返回到调用方,从而不会阻塞方法的调用方,但还会阻塞方法内的后续代码的执行,导致任务都按顺序执行。

上面的代码,无论是同步阻塞还是异步无阻塞,做出来的早餐中的食物很多都已经凉了。

3.异步并行

您希望立即启动若干独立的任务,然后等待处理它的结果。

吐司制作由异步操作(烤面包)和同步操作(添加黄油和果酱)组成。异步操作后跟同步操作的这种组合是一个异步操作。 换言之,如果操作的任何部分是异步的,整个操作就是异步的。

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!");
}
5.3C#异步编程完整示例

对于并行算法而言,你则需要多名厨师(或线程)。一名厨师煎鸡蛋,一名厨师煎培根,依次类推。 每名厨师将仅专注于一项任务。

异步并行准备的早餐大约花费了 20 分钟,此段代码不仅不会阻塞方法的调用方,还不会阻塞方法内的后续代码执行,由于一些任务并发运行,因此节约了时间。

4.高效地等待任务

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

其中一个 API 是 WhenAll,它将返回一个其参数列表中的所有任务都已完成时才完成的 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>。处理已完成任务的结果之后,可以从传递给 WhenAny 的任务列表中删除此已完成的任务。

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

5.异步异常

异步代码的读写风格与同步方法几乎一样,因此异步代码的异常处理也应该追求与同步代码几乎一样的读写风格。

例如,假设烤面包机在烤面包时着火了。 可通过修改 ToastBreadAsync 方法来模拟这种情况:

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(2000);
    Console.WriteLine("Fire! Toast is ruined!");
    throw new InvalidOperationException("The toaster is on fire");
    await Task.Delay(1000);
    Console.WriteLine("Remove toast from toaster");

    return new Toast();
}

6.最终异步并行代码

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();
        }
    }
}
5.3C#异步编程完整示例

异步准备的早餐的最终版本大约花费了 15 分钟,因为一些任务并行运行,并且代码同时监视多个任务,只在需要时才执行操作。

原创文章,作者:huoxiaoqiang,如若转载,请注明出处:https://www.huoxiaoqiang.com/csharp/csharplang/8137.html

(0)
上一篇 2021年5月3日 02:33
下一篇 2021年5月4日 23:55

相关推荐

  • 3.12C#类型之记录结构(record struct)和记录类(record class)

    1.记录声明 声明记录结构(值类型)或记录类(引用类型)的语法与声明结构或类的语法相似,可使用位置参数或属性语法声明记录类型。 记录类型可以是不可变类型,也可以是可变类型,但记录类型的发明主要是为创建自定义不可变类型提供简洁的语法。 编译器会为记录的每个位置参数自动创建public自动实现的属性,如果生成的自动实现的属…

    C#语言教程 2021年3月12日
    05540
  • 5.2C#异步编程场景

    C#异步编程场景分为 I/O 绑定(例如从网络请求数据、访问数据库或读取和写入到文件系统)和 CPU 绑定(例如执行成本高昂的计算)。 I/O 绑定场景:从 Web 服务下载数据 对于 I/O 绑定代码,等待一个在 async 方法中返回 Task 或 Task<…

    C#语言教程 2021年5月2日
    02630
  • 3.15C#语言的可访问性(Accessibility)

    访问修饰符 程序集 是通过在单个编译中编译一个或多个 .cs 文件而创建的 .dll 或 .exe。 调用方的位置 public protected internal protected internal private protected private 在类内 ✔️️ ✔️ ✔️ ✔️ ✔️ ✔️ 派生类(相同程序…

    C#语言教程 2021年3月15日
    05710

发表回复

登录后才能评论