**Task 异步编程模型(TAP)为异步代码提供了一种抽象。我们可以像往常一样按照顺序编写代码,每个语句看起来都会在下一个语句开始之前完成**。编译器会进行许多转换,因为其中一些语句可能会启动工作并返回表示正在进行的工作的 Task

这种语法的目标是使代码读起来像一连串语句,但执行顺序却复杂得多,它基于外部资源分配和任务完成时间。这类似于为包含异步任务的流程提供指令的方式。在本文中,将使用制作早餐的示例来演示 async 和 await 关键字如何使包含一系列异步指令的代码更易于理解。我们可以像以下列表一样编写指令来解释如何制作早餐:

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

如果你有烹饪经验,你会异步执行这些指令。你会先开始加热平底锅准备煎鸡蛋,然后开始煎培根。你会把面包放进烤箱,然后开始煮鸡蛋。在整个过程的每个步骤中,你都会启动一个任务,然后转向那些准备好需要你处理的任务。

制作早餐是一个很好的异步工作而非并行工作的例子。一个人(或线程)可以处理所有这些任务。延续早餐的比喻,一个人可以通过在第一个任务完成之前开始下一个任务来异步制作早餐。烹饪过程会继续进行,无论是否有人在观察。一旦你开始加热平底锅准备煎鸡蛋,你就可以开始煎培根。一旦培根开始煎,你就可以把面包放进烤箱。

对于并行算法(Parallel algorithm),你需要多个厨师(或线程)。一个人负责煎鸡蛋,一个人负责煎培根,以此类推。每个人专注于自己的任务。每个厨师(或线程)会同步地被阻塞,等待培根煎熟需要翻面,或者面包弹出来

现在,我们考虑将这些指令写成 C# 语句:

namespace AsyncBreakfast
{
    // 这些类在此示例中是故意为空的。它们只是用于演示的标记类,不包含属性,并且没有其他作用。
    internal class Bacon { }
    internal class Coffee { }
    internal class Egg { }
    internal class Juice { }
    internal class Toast { }

    class Program
    {
        static void Main(string[] args)
        {
            Coffee cup = PourCoffee();
            Console.WriteLine("咖啡已准备好");

            Egg eggs = FryEggs(2);
            Console.WriteLine("鸡蛋已煎好");

            Bacon bacon = FryBacon(3);
            Console.WriteLine("培根已煎好");

            Toast toast = ToastBread(2);
            ApplyButter(toast);
            ApplyJam(toast);
            Console.WriteLine("面包已烤好");

            Juice oj = PourOJ();
            Console.WriteLine("橙汁已倒好");
            Console.WriteLine("早餐已准备好!");
        }

        private static Juice PourOJ()
        {
            Console.WriteLine("倒橙汁");
            return new Juice();
        }

        private static void ApplyJam(Toast toast) =>
            Console.WriteLine("在面包上涂果酱");

        private static void ApplyButter(Toast toast) =>
            Console.WriteLine("在面包上涂黄油");

        private static Toast ToastBread(int slices)
        {
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("将一片面包放入烤面包机");
            }
            Console.WriteLine("开始烤面包...");
            Task.Delay(3000).Wait();
            Console.WriteLine("从烤面包机中取出面包");

            return new Toast();
        }

        private static Bacon FryBacon(int slices)
        {
            Console.WriteLine($"将{slices}片培根放入平底锅");
            Console.WriteLine("煎培根的第一面...");
            Task.Delay(3000).Wait();
            for (int slice = 0; slice < slices; slice++)
            {
                Console.WriteLine("翻转一片培根");
            }
            Console.WriteLine("煎培根的第二面...");
            Task.Delay(3000).Wait();
            Console.WriteLine("将培根放在盘子上");

            return new Bacon();
        }

        private static Egg FryEggs(int howMany)
        {
            Console.WriteLine("预热煎蛋锅...");
            Task.Delay(3000).Wait();
            Console.WriteLine($"打开{howMany}个鸡蛋");
            Console.WriteLine("煮鸡蛋...");
            Task.Delay(3000).Wait();
            Console.WriteLine("将鸡蛋放在盘子上");

            return new Egg();
        }

        private static Coffee PourCoffee()
        {
            Console.WriteLine("倒咖啡");
            return new Coffee();
        }
    }
}

Untitled

计算机不像人类一样解读这些指令。计算机会在每个语句上阻塞,直到工作完成后才继续执行下一个语句。这样会导致早餐准备时间很长,并且有些食物在上桌前就已经变凉了。