C# 零基础到精通教程 - 第十四章:异步编程——async/await 详解

第十三章我们学习了文件 I/O,知道了如何读写文件。但你可能注意到:当读写大文件或访问网络时,程序会"卡住"等待操作完成。这一章要学的异步编程,就是让程序在等待耗时操作时不会卡死,可以继续响应用户操作。


14.1 为什么需要异步编程?

14.1.1 同步编程的问题

csharp

复制代码
// 同步版本:UI 会卡死
void DownloadFile()
{
    Console.WriteLine("开始下载...");
    Thread.Sleep(5000);  // 模拟耗时操作
    Console.WriteLine("下载完成!");
}
// 在这 5 秒内,程序完全无响应

14.1.2 异步编程的解决方案

csharp

复制代码
// 异步版本:不会卡死
async Task DownloadFileAsync()
{
    Console.WriteLine("开始下载...");
    await Task.Delay(5000);  // 异步等待,不阻塞线程
    Console.WriteLine("下载完成!");
}
// 在等待期间,程序可以继续做其他事情

14.1.3 同步 vs 异步对比

text

复制代码
同步模型(一个线程):
┌─────────────────────────────────────────────────────────┐
│ 请求1 ████████████████▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
│ 请求2                              等待中...           │
│ 请求3                              等待中...           │
└─────────────────────────────────────────────────────────┘

异步模型(I/O 线程池):
┌─────────────────────────────────────────────────────────┐
│ 请求1 ████████████▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀      │
│ 请求2         ████████████▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀       │
│ 请求3                 ████████████▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀    │
└─────────────────────────────────────────────────────────┘

14.2 Task 和 Task<T>

14.2.1 Task 基础

csharp

复制代码
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

class TaskDemo
{
    static void Main()
    {
        Console.WriteLine("=== Task 基础演示 ===\n");
        
        // 1. 创建并启动 Task
        Task task1 = Task.Run(() =>
        {
            Console.WriteLine("Task 1 正在执行");
            Thread.Sleep(1000);
            Console.WriteLine("Task 1 完成");
        });
        
        // 2. 等待 Task 完成
        task1.Wait();
        
        // 3. 创建带返回值的 Task
        Task<int> task2 = Task.Run(() =>
        {
            Console.WriteLine("Task 2 正在计算...");
            Thread.Sleep(1000);
            return 42;
        });
        
        int result = task2.Result;  // 等待并获取结果
        Console.WriteLine($"Task 2 结果:{result}");
        
        // 4. 使用 Task.Factory 创建
        Task task3 = Task.Factory.StartNew(() =>
        {
            Console.WriteLine("Task 3 通过 Factory 创建");
        });
        task3.Wait();
        
        // 5. 使用 Task 构造函数(需要手动启动)
        Task task4 = new Task(() => Console.WriteLine("Task 4 手动启动"));
        task4.Start();
        task4.Wait();
    }
}

14.2.2 Task 的状态和属性

csharp

复制代码
class TaskStatusDemo
{
    static void Main()
    {
        Task task = Task.Run(() =>
        {
            Console.WriteLine("任务执行中...");
            Thread.Sleep(1000);
        });
        
        Console.WriteLine($"创建后状态:{task.Status}");  // WaitingToRun
        
        Thread.Sleep(100);
        Console.WriteLine($"执行中状态:{task.Status}");  // Running
        
        task.Wait();
        Console.WriteLine($"完成后状态:{task.Status}");  // RanToCompletion
        
        // 其他状态示例
        Task canceledTask = Task.Run(() =>
        {
            throw new OperationCanceledException();
        });
        
        try { canceledTask.Wait(); }
        catch { }
        Console.WriteLine($"取消状态:{canceledTask.Status}");  // Canceled
        
        Task faultedTask = Task.Run(() =>
        {
            throw new InvalidOperationException();
        });
        
        try { faultedTask.Wait(); }
        catch { }
        Console.WriteLine($"故障状态:{faultedTask.Status}");  // Faulted
        Console.WriteLine($"异常:{faultedTask.Exception?.InnerException?.Message}");
        
        // 常用属性
        Console.WriteLine($"\nTask 属性:");
        Console.WriteLine($"Completed: {task.IsCompleted}");
        Console.WriteLine($"Canceled: {task.IsCanceled}");
        Console.WriteLine($"Faulted: {task.IsFaulted}");
        Console.WriteLine($"Id: {task.Id}");
    }
}

14.3 async/await 基础

14.3.1 async/await 语法

csharp

复制代码
using System;
using System.Threading.Tasks;

class AsyncAwaitDemo
{
    // async 关键字标记异步方法
    // 返回值必须是 Task、Task<T> 或 void(仅限事件处理器)
    static async Task DoSomethingAsync()
    {
        Console.WriteLine("异步方法开始");
        
        // await 挂起方法,等待操作完成
        await Task.Delay(1000);
        
        Console.WriteLine("异步方法继续执行");
        await Task.Delay(1000);
        
        Console.WriteLine("异步方法结束");
    }
    
    // 带返回值的异步方法
    static async Task<int> CalculateAsync()
    {
        Console.WriteLine("开始计算...");
        await Task.Delay(500);
        return 100;
    }
    
    // 异步方法调用
    static async Task Main()
    {
        Console.WriteLine("=== async/await 基础 ===\n");
        
        // 调用异步方法(不等待)
        Task task = DoSomethingAsync();
        
        Console.WriteLine("主线程继续执行...");
        
        // 等待异步方法完成
        await task;
        
        // 获取返回值
        int result = await CalculateAsync();
        Console.WriteLine($"计算结果:{result}");
    }
}

14.3.2 async/await 执行流程

csharp

复制代码
class ExecutionFlowDemo
{
    static async Task Main()
    {
        Console.WriteLine($"1. 主线程开始 (ID: {Thread.CurrentThread.ManagedThreadId})");
        
        await DoWorkAsync();
        
        Console.WriteLine($"5. 主线程继续 (ID: {Thread.CurrentThread.ManagedThreadId})");
    }
    
    static async Task DoWorkAsync()
    {
        Console.WriteLine($"2. DoWorkAsync 开始 (ID: {Thread.CurrentThread.ManagedThreadId})");
        
        // await 前的代码在调用线程执行
        await Task.Delay(100);  // 切换到线程池线程
        
        // await 后的代码可能在任意线程执行
        Console.WriteLine($"4. await 后的代码 (ID: {Thread.CurrentThread.ManagedThreadId})");
    }
}
// 输出示例:
// 1. 主线程开始 (ID: 1)
// 2. DoWorkAsync 开始 (ID: 1)
// 5. 主线程继续 (ID: 1)
// 4. await 后的代码 (ID: 4)
// 注意:await 后的代码可能在主线程或线程池线程执行

14.4 异步等待方式

14.4.1 await、Wait、Result

csharp

复制代码
class WaitingDemo
{
    static async Task Main()
    {
        // 1. await:推荐方式
        await Task.Delay(1000);
        
        // 2. Wait:同步等待(会阻塞)
        Task task = Task.Delay(1000);
        task.Wait();
        
        // 3. Result:同步获取结果(会阻塞)
        Task<int> task2 = Task.Run(() => 42);
        int result = task2.Result;
        
        // 4. GetAwaiter().GetResult():同步等待,不包装异常
        Task<int> task3 = Task.Run(() => 42);
        int result2 = task3.GetAwaiter().GetResult();
        
        // 5. 等待多个任务
        Task task4 = Task.Delay(500);
        Task task5 = Task.Delay(1000);
        await Task.WhenAll(task4, task5);
        
        // 6. 等待任意一个任务
        Task task6 = Task.Delay(500);
        Task task7 = Task.Delay(1000);
        Task first = await Task.WhenAny(task6, task7);
        Console.WriteLine($"先完成的任务:{first.Id}");
        
        // 7. 延迟等待
        await Task.Delay(100);  // 100 毫秒
        
        // 8. 使用 CancellationToken
        using (var cts = new CancellationTokenSource(500))
        {
            try
            {
                await Task.Delay(1000, cts.Token);
            }
            catch (TaskCanceledException)
            {
                Console.WriteLine("任务被取消");
            }
        }
    }
}

14.4.2 Task.WhenAll / WhenAny

csharp

复制代码
class TaskCombinationDemo
{
    static async Task Main()
    {
        Console.WriteLine("=== 任务组合演示 ===\n");
        
        // 创建多个任务
        var tasks = new[]
        {
            GetDataAsync("用户数据", 2000),
            GetDataAsync("订单数据", 1500),
            GetDataAsync("产品数据", 1000)
        };
        
        // 1. 等待所有任务完成
        Console.WriteLine("等待所有任务完成...");
        string[] results = await Task.WhenAll(tasks);
        foreach (string result in results)
        {
            Console.WriteLine(result);
        }
        
        // 2. 等待任意一个任务完成
        Console.WriteLine("\n等待任意一个任务完成...");
        var fastTasks = new[]
        {
            Task.Delay(3000).ContinueWith(_ => "慢任务"),
            Task.Delay(2000).ContinueWith(_ => "中任务"),
            Task.Delay(1000).ContinueWith(_ => "快任务")
        };
        
        string firstResult = await await Task.WhenAny(fastTasks);
        Console.WriteLine($"最先完成:{firstResult}");
        
        // 3. 带超时的 WhenAll
        await WhenAllWithTimeout(tasks, 1500);
    }
    
    static async Task<string> GetDataAsync(string name, int delay)
    {
        await Task.Delay(delay);
        return $"{name} 完成(耗时 {delay}ms)";
    }
    
    static async Task WhenAllWithTimeout(Task[] tasks, int timeoutMs)
    {
        Task timeoutTask = Task.Delay(timeoutMs);
        Task completedTask = await Task.WhenAny(Task.WhenAll(tasks), timeoutTask);
        
        if (completedTask == timeoutTask)
        {
            Console.WriteLine($"\n超时!{timeoutMs}ms 内未完成所有任务");
        }
        else
        {
            Console.WriteLine("\n所有任务已完成");
        }
    }
}

14.5 异常处理

14.5.1 异步方法中的异常

csharp

复制代码
class AsyncExceptionDemo
{
    static async Task Main()
    {
        // 1. 使用 try-catch 捕获异常
        try
        {
            await ThrowExceptionAsync();
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine($"捕获到异常:{ex.Message}");
        }
        
        // 2. 多个异常的处理
        await HandleMultipleExceptions();
        
        // 3. 使用 await 等待时异常会抛出
        Task faultedTask = ThrowExceptionAsync();
        try
        {
            await faultedTask;
        }
        catch (InvalidOperationException)
        {
            Console.WriteLine("异常被捕获");
        }
        
        // 4. 不使用 await 时异常被忽略(危险!)
        Task ignoredTask = ThrowExceptionAsync();  // 异常被吞噬!
        
        // 5. 检查任务的异常
        Task task = ThrowExceptionAsync();
        await Task.Delay(100);  // 等待任务执行
        
        if (task.IsFaulted)
        {
            Console.WriteLine($"任务失败:{task.Exception?.InnerException?.Message}");
        }
    }
    
    static async Task ThrowExceptionAsync()
    {
        await Task.Delay(10);
        throw new InvalidOperationException("异步方法中的异常");
    }
    
    static async Task HandleMultipleExceptions()
    {
        Task task1 = Task.Run(() => throw new InvalidOperationException("异常1"));
        Task task2 = Task.Run(() => throw new ArgumentException("异常2"));
        Task task3 = Task.Run(() => 42);
        
        Task allTasks = Task.WhenAll(task1, task2, task3);
        
        try
        {
            await allTasks;
        }
        catch
        {
            // 当多个异常时,第一个被抛出
            Console.WriteLine($"第一个异常:{allTasks.Exception?.InnerException?.Message}");
            
            // 获取所有异常
            if (allTasks.Exception != null)
            {
                Console.WriteLine("所有异常:");
                foreach (var ex in allTasks.Exception.InnerExceptions)
                {
                    Console.WriteLine($"  - {ex.Message}");
                }
            }
        }
    }
}

14.5.2 异常处理最佳实践

csharp

复制代码
class AsyncExceptionBestPractice
{
    // ✅ 好的做法:使用 try-catch 包装 await
    async Task GoodPractice1()
    {
        try
        {
            await SomeOperationAsync();
        }
        catch (Exception ex)
        {
            LogError(ex);
            throw;  // 或处理
        }
    }
    
    // ❌ 错误做法:没有等待的任务会吞异常
    void BadPractice1()
    {
        SomeOperationAsync();  // 异常被忽略!
    }
    
    // ✅ 好的做法:记录未观察的异常
    void SetupGlobalExceptionHandling()
    {
        TaskScheduler.UnobservedTaskException += (sender, e) =>
        {
            Console.WriteLine($"未观察的异常:{e.Exception}");
            e.SetObserved();  // 标记为已处理
        };
    }
    
    // ✅ 好的做法:使用 ContinueWith 处理异常
    async Task GoodPractice2()
    {
        await SomeOperationAsync().ContinueWith(task =>
        {
            if (task.IsFaulted)
            {
                LogError(task.Exception);
            }
        });
    }
    
    async Task SomeOperationAsync()
    {
        await Task.Delay(10);
    }
    
    void LogError(Exception ex)
    {
        Console.WriteLine($"错误:{ex.Message}");
    }
}

14.6 异步方法的最佳实践

14.6.1 命名规范

csharp

复制代码
// ✅ 异步方法应以 Async 结尾
async Task LoadDataAsync() { }
async Task<string> FetchUserAsync(int id) { }
async ValueTask<int> GetCountAsync() { }

// ❌ 不要这样命名
async Task LoadData() { }

14.6.2 避免 async void

csharp

复制代码
class AsyncVoidDemo
{
    // ❌ 错误:async void 难调试、异常难捕获
    async void BadButtonClick()
    {
        await Task.Delay(100);
        throw new Exception("这个异常会崩溃程序");
    }
    
    // ✅ 正确:返回 Task
    async Task GoodButtonClick()
    {
        await Task.Delay(100);
        throw new Exception("这个异常可以被捕获");
    }
    
    // ✅ 唯一可以 async void 的场景:事件处理器
    async void Button_Click(object sender, EventArgs e)
    {
        try
        {
            await LoadDataAsync();
        }
        catch (Exception ex)
        {
            // 显示错误给用户
            Console.WriteLine($"错误:{ex.Message}");
        }
    }
    
    async Task LoadDataAsync()
    {
        await Task.Delay(100);
    }
}

14.6.3 ConfigureAwait

csharp

复制代码
class ConfigureAwaitDemo
{
    // 默认行为:尝试回到原始上下文(UI 线程)
    async Task DefaultBehavior()
    {
        // 在 UI 线程执行
        await Task.Delay(100);
        // 尝试回到 UI 线程
    }
    
    // 使用 ConfigureAwait(false):不需要回到原始上下文
    async Task ConfigureAwaitFalse()
    {
        // 在任何线程执行
        await Task.Delay(100).ConfigureAwait(false);
        // 继续在线程池线程执行
        // 适用于库代码、非 UI 代码
    }
    
    // 最佳实践:库代码使用 ConfigureAwait(false)
    async Task<string> LibraryMethodAsync()
    {
        using (var client = new HttpClient())
        {
            string result = await client.GetStringAsync("http://example.com")
                                        .ConfigureAwait(false);
            return result;
        }
    }
    
    // UI 代码:需要回到 UI 线程,不使用 ConfigureAwait(false)
    async Task UIButton_Click()
    {
        string data = await LibraryMethodAsync();  // 自动回到 UI 线程
        textBox.Text = data;  // 安全更新 UI
    }
}

14.6.4 避免死锁

csharp

复制代码
class DeadlockDemo
{
    // ❌ 死锁示例
    void Deadlock()
    {
        // UI 线程调用异步方法并使用 .Result
        var result = GetDataAsync().Result;  // 死锁!
    }
    
    // ✅ 解决方案1:使用 await(推荐)
    async Task Solution1()
    {
        var result = await GetDataAsync();
    }
    
    // ✅ 解决方案2:使用 ConfigureAwait(false)
    void Solution2()
    {
        var result = GetDataAsync().ConfigureAwait(false).GetAwaiter().GetResult();
    }
    
    // ✅ 解决方案3:完全异步化
    async Task Solution3()
    {
        await Task.Run(async () =>
        {
            var result = await GetDataAsync();
        });
    }
    
    async Task<string> GetDataAsync()
    {
        await Task.Delay(100);
        return "data";
    }
}

14.7 常用异步模式

14.7.1 异步工厂方法

csharp

复制代码
class AsyncFactoryDemo
{
    // 禁止:异步构造函数
    // public AsyncFactoryDemo() { await LoadAsync(); }  // ❌
    
    // ✅ 使用异步工厂方法
    private AsyncFactoryDemo() { }
    
    public static async Task<AsyncFactoryDemo> CreateAsync()
    {
        var instance = new AsyncFactoryDemo();
        await instance.InitializeAsync();
        return instance;
    }
    
    private async Task InitializeAsync()
    {
        await Task.Delay(100);
    }
}

// 使用
class Usage
{
    async Task UseAsync()
    {
        var instance = await AsyncFactoryDemo.CreateAsync();
    }
}

14.7.2 异步 Dispose

csharp

复制代码
class AsyncDisposableDemo : IAsyncDisposable
{
    private FileStream stream;
    
    public AsyncDisposableDemo(string path)
    {
        stream = new FileStream(path, FileMode.OpenOrCreate);
    }
    
    public async ValueTask DisposeAsync()
    {
        if (stream != null)
        {
            await stream.DisposeAsync();
        }
    }
}

// 使用
class Usage
{
    async Task UseAsync()
    {
        await using var disposable = new AsyncDisposableDemo("file.txt");
        // 使用 disposable
    }  // 自动调用 DisposeAsync
}

14.7.3 异步流(IAsyncEnumerable)

csharp

复制代码
class AsyncEnumerableDemo
{
    // 异步生成数据
    static async IAsyncEnumerable<int> GenerateNumbersAsync(int count)
    {
        for (int i = 1; i <= count; i++)
        {
            await Task.Delay(100);  // 模拟异步操作
            yield return i;
        }
    }
    
    // 异步消费数据
    static async Task ConsumeAsync()
    {
        Console.WriteLine("异步流开始:");
        
        await foreach (int number in GenerateNumbersAsync(10))
        {
            Console.WriteLine($"收到:{number}");
        }
        
        Console.WriteLine("完成");
    }
    
    static async Task Main()
    {
        await ConsumeAsync();
    }
}

14.8 并行与并发

14.8.1 并行任务执行

csharp

复制代码
class ParallelDemo
{
    static async Task Main()
    {
        Console.WriteLine("=== 并行执行演示 ===\n");
        
        // 1. 并发执行多个独立任务
        var tasks = new[]
        {
            ProcessAsync(1, 2000),
            ProcessAsync(2, 1500),
            ProcessAsync(3, 1000),
            ProcessAsync(4, 2500)
        };
        
        var stopwatch = Stopwatch.StartNew();
        await Task.WhenAll(tasks);
        stopwatch.Stop();
        
        Console.WriteLine($"\n总耗时:{stopwatch.ElapsedMilliseconds}ms");
        
        // 2. 限制并发数量
        await ProcessWithLimit(10, 3);
        
        // 3. 并行处理集合
        await ProcessCollectionParallel();
    }
    
    static async Task ProcessAsync(int id, int delay)
    {
        Console.WriteLine($"任务 {id} 开始");
        await Task.Delay(delay);
        Console.WriteLine($"任务 {id} 完成(耗时 {delay}ms)");
    }
    
    // 限制并发数量
    static async Task ProcessWithLimit(int totalCount, int maxConcurrent)
    {
        Console.WriteLine($"\n=== 并发限制 {maxConcurrent} ===\n");
        
        using (var semaphore = new SemaphoreSlim(maxConcurrent))
        {
            var tasks = new List<Task>();
            
            for (int i = 1; i <= totalCount; i++)
            {
                await semaphore.WaitAsync();
                
                int id = i;
                tasks.Add(Task.Run(async () =>
                {
                    try
                    {
                        Console.WriteLine($"任务 {id} 开始");
                        await Task.Delay(500);
                        Console.WriteLine($"任务 {id} 完成");
                    }
                    finally
                    {
                        semaphore.Release();
                    }
                }));
            }
            
            await Task.WhenAll(tasks);
        }
    }
    
    // 并行处理集合
    static async Task ProcessCollectionParallel()
    {
        var numbers = Enumerable.Range(1, 20);
        
        Console.WriteLine("\n=== 并行处理集合 ===\n");
        
        var tasks = numbers.Select(async n =>
        {
            int result = await ProcessNumberAsync(n);
            Console.WriteLine($"数字 {n} 的结果:{result}");
        });
        
        await Task.WhenAll(tasks);
    }
    
    static async Task<int> ProcessNumberAsync(int n)
    {
        await Task.Delay(50);
        return n * n;
    }
}

14.8.2 数据流处理

csharp

复制代码
using System.Threading.Tasks.Dataflow;

class DataflowDemo
{
    static async Task Main()
    {
        Console.WriteLine("=== 数据流处理 ===\n");
        
        // 创建数据流管道
        var transformBlock = new TransformBlock<int, int>(async n =>
        {
            await Task.Delay(100);
            return n * n;
        }, new ExecutionDataflowBlockOptions
        {
            MaxDegreeOfParallelism = 3  // 最多 3 个并发
        });
        
        var actionBlock = new ActionBlock<int>(result =>
        {
            Console.WriteLine($"结果:{result}");
        });
        
        // 连接块
        transformBlock.LinkTo(actionBlock, new DataflowLinkOptions { PropagateCompletion = true });
        
        // 发送数据
        for (int i = 1; i <= 10; i++)
        {
            await transformBlock.SendAsync(i);
            Console.WriteLine($"发送:{i}");
        }
        
        // 完成管道
        transformBlock.Complete();
        await actionBlock.Completion;
        
        Console.WriteLine("处理完成");
    }
}

14.9 取消和超时

14.9.1 CancellationToken 基础

csharp

复制代码
class CancellationDemo
{
    static async Task Main()
    {
        Console.WriteLine("=== 取消操作演示 ===\n");
        
        // 1. 手动取消
        using (var cts = new CancellationTokenSource())
        {
            var task = LongRunningOperationAsync(cts.Token);
            
            Console.WriteLine("按任意键取消...");
            Console.ReadKey();
            
            cts.Cancel();
            
            try
            {
                await task;
                Console.WriteLine("任务完成");
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("任务被取消");
            }
        }
        
        // 2. 超时自动取消
        await TimeoutOperation();
        
        // 3. 组合取消源
        await CombinedCancellation();
    }
    
    static async Task LongRunningOperationAsync(CancellationToken token)
    {
        for (int i = 1; i <= 10; i++)
        {
            token.ThrowIfCancellationRequested();  // 检查取消
            
            Console.WriteLine($"处理中... {i}/10");
            await Task.Delay(500, token);  // 传入 token
        }
        
        Console.WriteLine("操作完成");
    }
    
    static async Task TimeoutOperation()
    {
        Console.WriteLine("\n=== 超时演示 ===");
        
        using (var cts = new CancellationTokenSource(2000))  // 2 秒超时
        {
            try
            {
                await Task.Delay(3000, cts.Token);
            }
            catch (TaskCanceledException)
            {
                Console.WriteLine("操作超时");
            }
        }
    }
    
    static async Task CombinedCancellation()
    {
        Console.WriteLine("\n=== 组合取消演示 ===");
        
        using (var cts1 = new CancellationTokenSource())
        using (var cts2 = new CancellationTokenSource())
        {
            // 组合多个取消源
            var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cts1.Token, cts2.Token);
            linkedCts.CancelAfter(1000);  // 1 秒后取消
            
            try
            {
                await Task.Delay(2000, linkedCts.Token);
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("被其中一个取消源取消");
            }
        }
    }
}

14.9.2 取消时的清理

csharp

复制代码
class CancellationCleanupDemo
{
    static async Task Main()
    {
        using (var cts = new CancellationTokenSource())
        {
            var task = ProcessWithCleanupAsync(cts.Token);
            
            await Task.Delay(1500);
            cts.Cancel();
            
            try
            {
                await task;
            }
            catch (OperationCanceledException)
            {
                Console.WriteLine("任务被取消,清理已完成");
            }
        }
    }
    
    static async Task ProcessWithCleanupAsync(CancellationToken token)
    {
        try
        {
            for (int i = 1; i <= 10; i++)
            {
                token.ThrowIfCancellationRequested();
                Console.WriteLine($"步骤 {i}/10");
                await Task.Delay(500, token);
            }
        }
        catch (OperationCanceledException)
        {
            Console.WriteLine("执行清理...");
            await CleanupAsync();  // 清理资源
            throw;  // 重新抛出,让调用方知道被取消
        }
    }
    
    static async Task CleanupAsync()
    {
        await Task.Delay(100);
        Console.WriteLine("清理完成");
    }
}

14.10 综合示例

示例1:异步 Web 请求

csharp

复制代码
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net.Http;
using System.Threading.Tasks;

class AsyncWebRequestDemo
{
    static async Task Main()
    {
        Console.WriteLine("=== 异步 Web 请求演示 ===\n");
        
        // 1. 单个请求
        await FetchUrlAsync("https://www.baidu.com");
        
        // 2. 并发请求
        await FetchMultipleUrlsAsync();
        
        // 3. 带超时的请求
        await FetchWithTimeoutAsync();
        
        // 4. 重试机制
        await FetchWithRetryAsync();
    }
    
    static async Task FetchUrlAsync(string url)
    {
        using (var client = new HttpClient())
        {
            Console.WriteLine($"请求:{url}");
            var stopwatch = Stopwatch.StartNew();
            
            string content = await client.GetStringAsync(url);
            
            stopwatch.Stop();
            Console.WriteLine($"完成:{url}");
            Console.WriteLine($"耗时:{stopwatch.ElapsedMilliseconds}ms");
            Console.WriteLine($"内容长度:{content.Length} 字符\n");
        }
    }
    
    static async Task FetchMultipleUrlsAsync()
    {
        string[] urls =
        {
            "https://www.baidu.com",
            "https://www.qq.com",
            "https://www.163.com"
        };
        
        Console.WriteLine("=== 并发请求 ===\n");
        
        using (var client = new HttpClient())
        {
            var tasks = urls.Select(async url =>
            {
                var stopwatch = Stopwatch.StartNew();
                string content = await client.GetStringAsync(url);
                stopwatch.Stop();
                
                return new
                {
                    Url = url,
                    ContentLength = content.Length,
                    TimeMs = stopwatch.ElapsedMilliseconds
                };
            });
            
            var results = await Task.WhenAll(tasks);
            
            foreach (var result in results)
            {
                Console.WriteLine($"{result.Url}: {result.ContentLength} 字符, {result.TimeMs}ms");
            }
        }
    }
    
    static async Task FetchWithTimeoutAsync()
    {
        Console.WriteLine("\n=== 带超时请求 ===");
        
        using (var client = new HttpClient())
        {
            // 设置超时
            client.Timeout = TimeSpan.FromSeconds(5);
            
            try
            {
                string content = await client.GetStringAsync("https://httpbin.org/delay/10");
                Console.WriteLine($"成功:{content.Length} 字符");
            }
            catch (TaskCanceledException)
            {
                Console.WriteLine("请求超时(5 秒)");
            }
        }
    }
    
    static async Task FetchWithRetryAsync()
    {
        Console.WriteLine("\n=== 带重试请求 ===");
        
        int maxRetries = 3;
        int retryDelay = 1000;
        
        for (int i = 1; i <= maxRetries; i++)
        {
            try
            {
                using (var client = new HttpClient())
                {
                    string content = await client.GetStringAsync("https://httpbin.org/status/500");
                    Console.WriteLine($"成功:{content.Length} 字符");
                    return;
                }
            }
            catch (HttpRequestException ex)
            {
                Console.WriteLine($"尝试 {i}/{maxRetries} 失败:{ex.Message}");
                
                if (i < maxRetries)
                {
                    Console.WriteLine($"等待 {retryDelay}ms 后重试...");
                    await Task.Delay(retryDelay);
                }
            }
        }
        
        Console.WriteLine("所有重试都失败");
    }
}

示例2:异步文件处理

csharp

复制代码
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;

class AsyncFileProcessingDemo
{
    static async Task Main()
    {
        Console.WriteLine("=== 异步文件处理演示 ===\n");
        
        // 1. 异步读写文件
        await AsyncReadWriteFile();
        
        // 2. 并发处理多个文件
        await ProcessMultipleFilesAsync();
        
        // 3. 异步流处理大文件
        await ProcessLargeFileAsync();
        
        // 4. 带进度的文件复制
        await CopyFileWithProgressAsync();
    }
    
    static async Task AsyncReadWriteFile()
    {
        string testFile = "test_async.txt";
        
        // 异步写入
        string content = "Hello, Async!\n第二行\n第三行";
        await File.WriteAllTextAsync(testFile, content, Encoding.UTF8);
        Console.WriteLine($"写入完成:{testFile}");
        
        // 异步读取
        string readContent = await File.ReadAllTextAsync(testFile, Encoding.UTF8);
        Console.WriteLine($"读取内容:\n{readContent}");
        
        // 异步追加
        await File.AppendAllTextAsync(testFile, "\n追加的行", Encoding.UTF8);
        Console.WriteLine("追加完成");
    }
    
    static async Task ProcessMultipleFilesAsync()
    {
        Console.WriteLine("\n=== 并发处理多个文件 ===");
        
        var files = new[] { "file1.txt", "file2.txt", "file3.txt" };
        
        // 创建测试文件
        foreach (var file in files)
        {
            await File.WriteAllTextAsync(file, $"这是文件 {file} 的内容\n行2\n行3", Encoding.UTF8);
        }
        
        // 并发处理
        var tasks = files.Select(async file =>
        {
            var lines = await File.ReadAllLinesAsync(file, Encoding.UTF8);
            int lineCount = lines.Length;
            int charCount = string.Join("", lines).Length;
            
            return new
            {
                File = file,
                LineCount = lineCount,
                CharCount = charCount
            };
        });
        
        var results = await Task.WhenAll(tasks);
        
        foreach (var result in results)
        {
            Console.WriteLine($"{result.File}: {result.LineCount} 行, {result.CharCount} 字符");
        }
    }
    
    static async Task ProcessLargeFileAsync()
    {
        Console.WriteLine("\n=== 异步流处理大文件 ===");
        
        string largeFile = "large_file.txt";
        
        // 创建大文件(1000 行)
        using (var writer = File.CreateText(largeFile))
        {
            for (int i = 1; i <= 1000; i++)
            {
                await writer.WriteLineAsync($"Line {i}: This is a test line for async processing.");
            }
        }
        Console.WriteLine($"创建大文件:{largeFile}(1000 行)");
        
        // 异步逐行处理
        int processedCount = 0;
        using (var reader = File.OpenText(largeFile))
        {
            string line;
            while ((line = await reader.ReadLineAsync()) != null)
            {
                processedCount++;
                if (processedCount % 200 == 0)
                {
                    Console.WriteLine($"已处理 {processedCount} 行");
                }
                // 模拟处理
                await Task.Delay(1);
            }
        }
        
        Console.WriteLine($"处理完成,共 {processedCount} 行");
    }
    
    static async Task CopyFileWithProgressAsync()
    {
        Console.WriteLine("\n=== 带进度的文件复制 ===");
        
        string source = "large_file.txt";
        string dest = "copied_file.txt";
        
        if (!File.Exists(source))
        {
            Console.WriteLine("源文件不存在");
            return;
        }
        
        FileInfo fi = new FileInfo(source);
        long totalBytes = fi.Length;
        long bytesRead = 0;
        
        using (FileStream sourceStream = new FileStream(source, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, useAsync: true))
        using (FileStream destStream = new FileStream(dest, FileMode.Create, FileAccess.Write, FileShare.None, 8192, useAsync: true))
        {
            byte[] buffer = new byte[8192];
            int bytes;
            
            while ((bytes = await sourceStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
            {
                await destStream.WriteAsync(buffer, 0, bytes);
                bytesRead += bytes;
                
                int percent = (int)((double)bytesRead / totalBytes * 100);
                Console.Write($"\r进度:{percent}%");
            }
        }
        
        Console.WriteLine($"\n复制完成:{source} → {dest}");
    }
}

示例3:异步 API 服务(模拟)

csharp

复制代码
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

// 模拟的 API 服务
class AsyncApiService
{
    private readonly ConcurrentDictionary<int, Task<string>> cache = new ConcurrentDictionary<int, Task<string>>();
    private readonly Random random = new Random();
    
    // 获取用户数据(带缓存)
    public async Task<string> GetUserAsync(int userId)
    {
        Console.WriteLine($"[API] 请求用户 {userId}");
        
        // 检查缓存
        if (cache.TryGetValue(userId, out var cachedTask))
        {
            Console.WriteLine($"[API] 用户 {userId} 从缓存返回");
            return await cachedTask;
        }
        
        // 模拟网络请求
        var task = FetchUserFromApiAsync(userId);
        cache.TryAdd(userId, task);
        
        try
        {
            return await task;
        }
        catch
        {
            cache.TryRemove(userId, out _);
            throw;
        }
    }
    
    private async Task<string> FetchUserFromApiAsync(int userId)
    {
        // 模拟网络延迟
        await Task.Delay(random.Next(500, 1500));
        
        // 模拟失败(10% 概率)
        if (random.Next(10) == 0)
        {
            throw new Exception("网络错误");
        }
        
        return $"User_{userId}: Name=User{userId}, Age={20 + userId % 30}";
    }
    
    // 批量获取用户
    public async Task<List<string>> GetUsersAsync(int[] userIds, IProgress<int> progress = null)
    {
        var results = new List<string>();
        var semaphore = new SemaphoreSlim(5);  // 限制并发 5 个
        
        var tasks = userIds.Select(async id =>
        {
            await semaphore.WaitAsync();
            try
            {
                var user = await GetUserAsync(id);
                progress?.Report(id);
                return user;
            }
            finally
            {
                semaphore.Release();
            }
        });
        
        var users = await Task.WhenAll(tasks);
        return new List<string>(users);
    }
}

// 进度报告器
class ProgressReporter : IProgress<int>
{
    private int total;
    private int completed;
    
    public ProgressReporter(int total)
    {
        this.total = total;
    }
    
    public void Report(int userId)
    {
        completed++;
        Console.WriteLine($"进度:{completed}/{total} ({completed * 100 / total}%)");
    }
}

// 使用示例
class Program
{
    static async Task Main()
    {
        Console.WriteLine("=== 异步 API 服务演示 ===\n");
        
        var api = new AsyncApiService();
        
        // 1. 单用户请求
        Console.WriteLine("1. 单用户请求:");
        var user = await api.GetUserAsync(1);
        Console.WriteLine($"结果:{user}\n");
        
        // 2. 缓存测试
        Console.WriteLine("2. 缓存测试(第二次请求):");
        user = await api.GetUserAsync(1);
        Console.WriteLine($"结果:{user}\n");
        
        // 3. 并发请求
        Console.WriteLine("3. 并发请求 10 个用户:");
        var userIds = new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
        var stopwatch = System.Diagnostics.Stopwatch.StartNew();
        
        var users = await api.GetUsersAsync(userIds, new ProgressReporter(userIds.Length));
        
        stopwatch.Stop();
        
        Console.WriteLine($"\n完成,耗时:{stopwatch.ElapsedMilliseconds}ms");
        foreach (var u in users)
        {
            Console.WriteLine($"  {u}");
        }
    }
}

14.11 常见错误与陷阱

错误1:异步方法不使用 await

csharp

复制代码
// ❌ 错误:没有 await,方法会立即返回
async Task BadMethod()
{
    SomeAsyncOperation();  // 异常被吞噬
    return;
}

// ✅ 正确:使用 await
async Task GoodMethod()
{
    await SomeAsyncOperation();
}

// 如果有返回值
async Task<int> BadMethod2()
{
    return SomeAsyncOperation();  // 编译错误
}

错误2:在 using 块中不等待异步操作

csharp

复制代码
// ❌ 错误:using 块结束时会释放资源,但异步操作还在执行
async Task BadUsing()
{
    using (var client = new HttpClient())
    {
        return await client.GetStringAsync("http://example.com");  // 正确
    }
    // 实际上这里才是正确的,上面是对的
}

// ❌ 真正的错误:没有等待就退出 using
async Task BadUsing2()
{
    using (var stream = new FileStream("file.txt", FileMode.Open))
    {
        var task = stream.ReadAsync(buffer, 0, buffer.Length);
    }  // 流被释放,但读取可能还在进行
}

错误3:阻塞异步代码

csharp

复制代码
// ❌ 错误:使用 .Result 或 .Wait() 可能导致死锁
void BadMethod()
{
    var result = GetDataAsync().Result;  // 可能导致死锁
    task.Wait();  // 也可能死锁
}

// ✅ 正确:一直使用 await
async Task GoodMethod()
{
    var result = await GetDataAsync();
}

错误4:忘记捕获异常

csharp

复制代码
// ❌ 错误:没有捕获异常
void BadMethod()
{
    Task.Run(() => throw new Exception());  // 异常被忽略
}

// ✅ 正确:观察异常
async Task GoodMethod()
{
    try
    {
        await Task.Run(() => throw new Exception());
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

14.12 本章总结

核心知识点导图

text

复制代码
异步编程
├── Task 和 Task<T>
│   ├── Task.Run() 创建
│   ├── 状态属性
│   └── 等待方式
├── async/await
│   ├── 异步方法定义
│   ├── 执行流程
│   └── 返回值
├── 异常处理
│   ├── try-catch
│   ├── Task.Exception
│   └── 未观察的异常
├── 组合操作
│   ├── Task.WhenAll
│   ├── Task.WhenAny
│   └── 并发限制
├── 取消和超时
│   ├── CancellationToken
│   ├── CancellationTokenSource
│   └── 超时实现
└── 最佳实践
    ├── 命名规范(Async 后缀)
    ├── 避免 async void
    ├── ConfigureAwait
    └── 避免死锁

选择指南

场景 推荐
I/O 操作(文件、网络) async/await
CPU 密集型操作 Task.Run + async/await
UI 响应性 使用 async/await
库代码 ConfigureAwait(false)
并发执行多个任务 Task.WhenAll
限流控制 SemaphoreSlim + WhenAll

14.13 练习题

基础题

  1. 编写一个异步方法,模拟下载文件,使用 Task.Delay 模拟耗时。

  2. 使用 Task.WhenAll 并发执行 5 个异步任务,并计算总耗时。

  3. 实现一个带重试机制的异步方法,最多重试 3 次。

应用题

  1. 实现一个异步缓存类:

    • 支持异步获取数据

    • 支持缓存过期时间

    • 支持并发访问时的缓存穿透保护

  2. 实现一个限流器:

    • 限制同时执行的异步操作数量

    • 支持等待队列

    • 支持超时

挑战题

  1. 实现一个异步消息队列:

    • 支持生产者-消费者模式

    • 支持背压控制

    • 支持优雅关闭

  2. 实现一个异步批量处理器:

    • 收集请求后批量处理

    • 支持缓冲区大小和间隔时间

    • 支持处理失败重试

相关推荐
神仙别闹3 分钟前
基于C#实现(WinForm)求解SIN(X)数值分析
c#
努力努力再努力wz3 小时前
【Qt入门系列】:按钮组件全解析:从 QAbstractButton 到快捷键事件、单选与复选机制
c语言·开发语言·数据结构·c++·git·qt·github
skywalk81634 小时前
言知(Yanzhi)系统提升建议报告和完工报告 by AutoCoder
开发语言·编程
yunn_4 小时前
单例模式两种实现方法
开发语言·c++·单例模式
我材不敲代码4 小时前
Python基础:列表详解、增删改查及常用高阶操作
开发语言·windows·python
AI玫瑰助手4 小时前
Python运算符:成员运算符(in/not in)的使用场景
开发语言·python·信息可视化
AI人工智能+电脑小能手5 小时前
【大白话说Java面试题 第77题】【Mysql篇】第7题:回表查询与全表扫描的区别?
java·开发语言·数据库·mysql·面试
水木流年追梦5 小时前
大模型入门-大模型分布式训练2
开发语言·分布式·python·算法·正则表达式·prompt
口袋里のInit5 小时前
基础知识——ARM M核入栈出栈流程
开发语言·arm开发