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