第十三章我们学习了文件 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 练习题
基础题
-
编写一个异步方法,模拟下载文件,使用
Task.Delay模拟耗时。 -
使用
Task.WhenAll并发执行 5 个异步任务,并计算总耗时。 -
实现一个带重试机制的异步方法,最多重试 3 次。
应用题
-
实现一个异步缓存类:
-
支持异步获取数据
-
支持缓存过期时间
-
支持并发访问时的缓存穿透保护
-
-
实现一个限流器:
-
限制同时执行的异步操作数量
-
支持等待队列
-
支持超时
-
挑战题
-
实现一个异步消息队列:
-
支持生产者-消费者模式
-
支持背压控制
-
支持优雅关闭
-
-
实现一个异步批量处理器:
-
收集请求后批量处理
-
支持缓冲区大小和间隔时间
-
支持处理失败重试
-