C#-异步

103 阅读4分钟

想象你是个独居的打工人,某天早上要给自己做顿丰盛早餐:

  1. 煮咖啡(3分钟)
  2. 煎蛋(6分钟)
  3. 煎培根(6分钟)
  4. 烤面包+抹果酱(5分钟)
  5. 倒橙汁(1分钟)

💣 同步灾难版(30分钟): 就像强迫症一样,你必须盯着咖啡壶煮完才能开火煎蛋,煎蛋时连面包都不敢放进烤箱。所有事情排长队,面包烤好时咖啡都凉了,妥妥的「早餐吃成早午餐」。

异步觉醒版(20分钟): 你突然掌握了时间管理术!咖啡壶开始煮就立刻热煎锅,煎蛋时培根也下锅,面包塞进烤箱后还能抽空摆盘。就像开了分身术,但依然手忙脚乱——因为每次都要等当前任务完成才能继续(await)

🚀 并发进阶版(6分钟): 此刻你化身厨房指挥官!咖啡壶、煎锅、烤箱同时启动,用三个魔法咒语记录任务进度:

var 煎蛋任务 = 开火煎蛋Async();
var 培根任务 = 煎培根Async();
var 烤面包任务 = 烤面包抹酱Async();

然后像等外卖一样,哪个先好就先处理哪个:

while(任务列表.Count > 0){
    var 完成的任务 = await Task.WhenAny(任务列表);
    if(完成的任务 == 煎蛋任务) 摆盘鸡蛋();
    //...其他任务同理
}

💡 核心秘籍: • async/await就像智能闹钟:设定好后不用干等,先去忙别的 • Task对象是「厨房计时器」,帮我们追踪每个料理的状态 • WhenAny像外卖平台的「最快送达」提示,WhenAll则是「等所有外卖到齐」 • 即使某个任务烧焦了(抛出异常),也能第一时间发现处理

🥪 早餐哲学启示: • 异步不是多线程(不需要雇多个厨师) • 代码像菜谱一样易读,却暗藏高效的时间魔法 • 现代程序就像快餐店,异步能让收银台不卡顿、后厨不空等

最后送上程序员早餐秘诀:不要用.Wait()干等面包烤好,小心触发烟雾报警(线程阻塞)!🚒用await才能优雅地边烤面包边刷手机~

C# 异步编程的经典案例——以“做早餐”为例


案例背景 🌅

假设程序员小明要在一个早晨完成 5 件事:煮咖啡、煎蛋、煎培根、烤面包、倒橙汁。如果按同步方式处理(一件件做),总耗时约 30 分钟;但用异步编程优化后,只需 6 分钟!这个例子生动体现了异步编程的核心理念:“不干等,边等边做”


同步 vs 异步的代码对比

1. 同步灾难版(耗时 30 分钟)

Coffee cup = PourCoffee();  // 煮咖啡(3分钟)
Egg eggs = FryEggs(2);      // 煎蛋(6分钟)
Bacon bacon = FryBacon(3);  // 煎培根(6分钟)
Toast toast = ToastBread(2); // 烤面包(5分钟)
Juice oj = PourOJ();        // 倒橙汁(1分钟)

问题:每个任务必须等前一个完成才能开始,咖啡凉了面包还没烤好。

2. 异步优化版(耗时 6 分钟)

// 同时启动所有任务
var coffeeTask = MakeCoffeeAsync();
var eggsTask = FryEggsAsync();
var baconTask = FryBaconAsync();
var toastTask = ToastBreadAsync();

// 等待所有任务完成
await Task.WhenAll(coffeeTask, eggsTask, baconTask, toastTask);

// 最后倒橙汁
Juice oj = PourOJ();

关键点
• 使用 Task.WhenAll 并行执行耗时操作
• 异步方法用 async/await 标记,避免阻塞主线程
• 总耗时 ≈ 最长任务时间(煎蛋/煎培根的 6 分钟)


其他经典应用场景

  1. 文件异步读写

    public async Task SaveLogAsync(string message) {
        await File.WriteAllTextAsync("log.txt", $"{DateTime.Now}: {message}");
    }
    

    • 优势:写入文件时程序可继续响应其他操作。

  2. 网络请求并行处理

    var tasks = new List<Task<string>>();
    tasks.Add(client.GetStringAsync("https://api1.com"));
    tasks.Add(client.GetStringAsync("https://api2.com"));
    string[] results = await Task.WhenAll(tasks);
    

    • 同时请求多个接口,提升吞吐量。

  3. 流式数据处理(IAsyncEnumerable)

    public async IAsyncEnumerable<int> FetchDataStreamAsync() {
        for (int i = 0; i < 1000000; i++) {
            await Task.Delay(100); // 模拟分批获取
            yield return i;        // 逐条返回,不占满内存
        }
    }
    

    • 适合海量数据场景(如日志分析)。


避坑指南 ⚠️

不要用 .Result.Wait():会导致死锁(如 GUI 线程阻塞)
异常处理:用 try-catch 包裹 await,避免静默失败
避免 async void:除事件处理器外,一律用 async Task


总结

异步编程的核心是通过 Task 管理“后台任务清单”,用 await 实现“挂起-恢复”机制。想象你点了一杯咖啡,服务员给你一个取餐号(Task<bool>),然后你可以去干别的事(比如看手机)。当咖啡做好(异步操作完成),服务员叫你的号(await 结束),你拿到咖啡(bool 值)继续喝。如果没有 await,你就一直站在柜台前干等,无法做其他事情(阻塞线程)。