using System; using System.Diagnostics; using System.Threading.Tasks; namespace BreakfastMakerSystem { public class Item<T> { public string Name { get; } // 物品名称 (公开只读) // 例如: "咖啡", "鸡蛋" public T Spec { get; } // 物品规格 (泛型,公开只读) // 例如: 对于咖啡, T是int, Spec是容量(ml); 对于土豆, T是string, Spec是描述。 public Item(string name, T spec) { Name = name; Spec = spec; } // 构造函数,用于初始化物品。 } public class BreakfastMaker { #region 委托与事件定义 public delegate Task CookingStep(); // 定义一个委托,用于封装所有无参数、返回Task的烹饪步骤。 // 委托就像一个方法的“模板”或“签名”,所有符合这个签名的方法都可以被它引用。 // 返回 Task 是为了适配异步方法。 public delegate void StepStartedHandler(string stepName); // 定义步骤开始事件的委托。 // 它规定了所有订阅 StepStarted 事件的方法必须接受一个 string 参数 (步骤名) 且无返回值。 public delegate void StepCompletedHandler(string stepName, TimeSpan duration); // 定义步骤完成事件的委托。 // 它规定了所有订阅 StepCompleted 事件的方法必须接受 string (步骤名) 和 TimeSpan (耗时) 两个参数。 public event StepStartedHandler StepStarted; // 步骤开始事件。 // 外部代码可以 "订阅" (+=) 这个事件,当事件被 "触发" 时,所有订阅的方法都会被调用。 // 这是一种典型的 "发布-订阅" 模式,实现了逻辑解耦。 public event StepCompletedHandler StepCompleted; // 步骤完成事件。 #endregion #region 异步制作方法 public async Task PourCoffeeAsync(Item<int> coffee) { Stopwatch stopwatch = new(); StepStarted?.Invoke("倒咖啡"); // 触发步骤开始事件,通知所有订阅者 await Task.Delay(500); // 使用 Task.Delay 模拟倒咖啡的耗时操作 (500毫秒) Console.WriteLine($"倒入 {coffee.Spec}ml {coffee.Name}"); stopwatch.Stop(); StepCompleted?.Invoke("倒咖啡", stopwatch.Elapsed); // 触发步骤完成事件,并传递耗时 } // 异步倒咖啡 public async Task FryEggsAsync(Item<int> eggs) { var stopwatch = Stopwatch.StartNew(); StepStarted?.Invoke("煎鸡蛋"); for (int i = 0; i < eggs.Spec; i++) { Console.WriteLine($"开始煎第 {i + 1} 个鸡蛋..."); await Task.Delay(1500); // 模拟煎一个鸡蛋耗时1.5秒 Console.WriteLine($"煎好第 {i + 1} 个鸡蛋"); } // 根据鸡蛋数量,循环模拟煎鸡蛋的过程 stopwatch.Stop(); StepCompleted?.Invoke("煎鸡蛋", stopwatch.Elapsed); } // 异步煎鸡蛋 public async Task BakeHashBrownsAsync(Item<string> potato) { var stopwatch = Stopwatch.StartNew(); StepStarted?.Invoke("烤薯饼"); await Task.Delay(3000); // 模拟烤薯饼的耗时 (3秒) Console.WriteLine($"用 {potato.Spec} 烤好薯饼"); stopwatch.Stop(); StepCompleted?.Invoke("烤薯饼", stopwatch.Elapsed); } // 异步烤薯饼 public async Task PlateAsync() { var stopwatch = Stopwatch.StartNew(); StepStarted?.Invoke("装盘"); await Task.Delay(1000); // 模拟装盘耗时 Console.WriteLine("将鸡蛋和薯饼装入餐盘"); stopwatch.Stop(); StepCompleted?.Invoke("装盘", stopwatch.Elapsed); } // 异步装盘 public async Task PourJuiceAsync(Item<string> juice) { var stopwatch = Stopwatch.StartNew(); StepStarted?.Invoke("倒果汁"); await Task.Delay(800); // 模拟倒果汁耗时 Console.WriteLine($"倒入 {juice.Spec}"); stopwatch.Stop(); StepCompleted?.Invoke("倒果汁", stopwatch.Elapsed); } // 异步倒果汁 #endregion } class Program { static async Task Main(string[] args) // Main 方法标记为 async Task,因为我们需要在其中使用 await 来等待异步操作完成。 { Console.WriteLine("--- 早餐制作流程管理系统启动 ---"); var totalStopwatch = Stopwatch.StartNew(); // 创建一个总计时器,计算整个流程的耗时 // (1) 创建 BreakfastMaker 实例和物品实例 var maker = new BreakfastMaker(); var coffee = new Item<int>("咖啡", 200); var eggs = new Item<int>("鸡蛋", 2); var potato = new Item<string>("土豆", "中等大小土豆"); var juice = new Item<string>("果汁", "苹果汁"); // (2) 订阅事件 maker.StepStarted += (stepName) => { Console.ForegroundColor = ConsoleColor.Yellow; // Console.ForegroundColor 可以在控制台输出不同颜色的文本,便于观察 Console.WriteLine($"[开始] {stepName}"); Console.ResetColor(); }; maker.StepCompleted += (stepName, duration) => { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine($"[完成] {stepName} (耗时: {duration.TotalSeconds:F1} 秒)"); // duration.TotalSeconds: 将 TimeSpan 转换为秒,并保留小数部分。F1表示格式化为1位小数。 Console.ResetColor(); }; // 使用 Lambda 表达式为事件添加处理逻辑,当事件被触发时,这些代码块会被执行。 // (3) 组织与执行步骤 Console.WriteLine("\n--- 开始制作早餐 ---"); // --- 阶段 1 (并行执行) --- Console.WriteLine("\n--- 阶段 1: 并行准备咖啡、鸡蛋和薯饼 ---"); Task pourCoffeeTask = maker.PourCoffeeAsync(coffee); Task fryEggsTask = maker.FryEggsAsync(eggs); Task bakeHashBrownsTask = maker.BakeHashBrownsAsync(potato); // 这三个步骤(倒咖啡、煎鸡蛋、烤薯饼)没有相互依赖,可以同时进行。 // 我们为每个步骤创建一个 Task。Task在这里可以理解为一个“工作票据”,代表一个将要完成的工作。 await Task.WhenAll(pourCoffeeTask, fryEggsTask, bakeHashBrownsTask); // Task.WhenAll 会等待传入的所有 Task 都完成后再继续执行。 // 这就是实现并行执行的关键。程序不会在这里卡住,而是会“让出”线程去做其他事, // 等到这三个任务都完成后,再回到这里继续往下走。 Console.WriteLine("--- 阶段 1 全部完成 ---"); // --- 阶段 2 (顺序执行) --- Console.WriteLine("\n--- 阶段 2: 装盘 ---"); await maker.PlateAsync(); // 装盘必须在煎鸡蛋和烤薯饼之后。由于上一步的 await Task.WhenAll 保证了这两个任务已完成, // 所以我们可以在这里安全地执行装盘。 Console.WriteLine("--- 阶段 2 完成 ---"); // --- 阶段 3 (顺序执行) --- Console.WriteLine("\n--- 阶段 3: 倒果汁 ---"); await maker.PourJuiceAsync(juice); // 倒果汁在装盘之后进行。 Console.WriteLine("--- 阶段 3 完成 ---"); totalStopwatch.Stop(); // 停止总计时器 Console.WriteLine("\n--- 早餐制作完成 ---"); Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine($"\n>>> 总耗时: {totalStopwatch.Elapsed.TotalSeconds:F1} 秒"); Console.ResetColor(); Console.WriteLine("\n按任意键退出..."); Console.ReadKey(); } } }

