名称: csharp-async-patterns 用户可调用: false 描述: 当需要进行 C# 异步编程时使用,包括 async/await、Task、ValueTask、ConfigureAwait 和异步流,用于构建响应式应用程序。 允许工具:
- 读取
- 写入
- 编辑
- 搜索
- 全局匹配
- Bash
C# 异步模式
C# 中的异步编程使编写响应式应用程序成为可能,高效处理 I/O 绑定和 CPU 绑定操作而不阻塞线程。async/await 模式提供了一种简单的方法来编写看起来和行为像同步代码的异步代码。
Async/Await 基础
async 和 await 关键字将看起来同步的代码转换为处理异步操作的状态机。
using System;
using System.Net.Http;
using System.Threading.Tasks;
public class AsyncBasics
{
// 基本异步方法
public async Task<string> FetchDataAsync(string url)
{
using var client = new HttpClient();
// await 暂停执行直到收到响应
string content = await client.GetStringAsync(url);
return content;
}
// 没有返回值的异步方法
public async Task ProcessDataAsync()
{
await Task.Delay(1000); // 模拟异步工作
Console.WriteLine("处理完成");
}
// 多个 await
public async Task<int> CalculateSumAsync()
{
int value1 = await GetValueAsync(1);
int value2 = await GetValueAsync(2);
int value3 = await GetValueAsync(3);
return value1 + value2 + value3;
}
private async Task<int> GetValueAsync(int id)
{
await Task.Delay(100);
return id * 10;
}
// 带有异常处理的异步方法
public async Task<string> SafeFetchAsync(string url)
{
try
{
using var client = new HttpClient();
return await client.GetStringAsync(url);
}
catch (HttpRequestException ex)
{
Console.WriteLine($"请求失败: {ex.Message}");
return string.Empty;
}
}
// 调用异步方法
public async Task DemoAsync()
{
// 等待结果
string data = await FetchDataAsync("https://api.example.com");
// 发射后不管(不推荐)
_ = ProcessDataAsync();
// 等待完成
await ProcessDataAsync();
}
}
Task 和 Task<T>
Task 表示异步操作,并提供组合、延续和错误处理的方法。
using System;
using System.Threading;
using System.Threading.Tasks;
public class TaskExamples
{
// 创建任务
public void CreateTasks()
{
// Task.Run 用于 CPU 绑定工作
Task<int> task1 = Task.Run(() =>
{
Thread.Sleep(1000);
return 42;
});
// Task.FromResult 用于已知值
Task<int> task2 = Task.FromResult(100);
// Task.CompletedTask 用于无返回值操作
Task task3 = Task.CompletedTask;
// TaskCompletionSource 用于手动控制
var tcs = new TaskCompletionSource<string>();
Task<string> task4 = tcs.Task;
tcs.SetResult("完成");
}
// 任务组合
public async Task<string> ComposeTasks()
{
// 顺序执行
int result1 = await Task1Async();
int result2 = await Task2Async(result1);
// 并行执行
Task<int> t1 = Task1Async();
Task<int> t2 = Task2Async(10);
await Task.WhenAll(t1, t2);
return $"结果: {t1.Result}, {t2.Result}";
}
// Task.WhenAll - 等待所有任务
public async Task<int[]> WhenAllExample()
{
var tasks = new[]
{
Task.Run(() => ComputeValue(1)),
Task.Run(() => ComputeValue(2)),
Task.Run(() => ComputeValue(3))
};
int[] results = await Task.WhenAll(tasks);
return results;
}
// Task.WhenAny - 等待第一个任务
public async Task<int> WhenAnyExample()
{
var task1 = DelayedValue(1000, 1);
var task2 = DelayedValue(2000, 2);
var task3 = DelayedValue(500, 3);
Task<int> completed = await Task.WhenAny(task1, task2, task3);
return await completed;
}
// 取消支持
public async Task<string> CancellableOperation(
CancellationToken cancellationToken)
{
for (int i = 0; i < 10; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(100, cancellationToken);
Console.WriteLine($"步骤 {i + 1}");
}
return "已完成";
}
// 辅助方法
private Task<int> Task1Async() => Task.FromResult(10);
private Task<int> Task2Async(int value) =>
Task.FromResult(value * 2);
private int ComputeValue(int x) => x * x;
private async Task<int> DelayedValue(int delay, int value)
{
await Task.Delay(delay);
return value;
}
}
ValueTask 和 ValueTask<T>
ValueTask 为经常同步完成的操作提供更好的性能,避免堆分配。
using System;
using System.Threading.Tasks;
public class ValueTaskExamples
{
private readonly Dictionary<string, string> _cache =
new Dictionary<string, string>();
// ValueTask 用于缓存操作
public ValueTask<string> GetValueAsync(string key)
{
// 同步路径 - 无分配
if (_cache.TryGetValue(key, out string? value))
{
return new ValueTask<string>(value);
}
// 异步路径
return new ValueTask<string>(FetchFromDatabaseAsync(key));
}
private async Task<string> FetchFromDatabaseAsync(string key)
{
await Task.Delay(100); // 模拟数据库调用
string value = $"值 for {key}";
_cache[key] = value;
return value;
}
// 在 Task 和 ValueTask 之间转换
public async ValueTask<int> ConversionExample()
{
// 从 Task 创建 ValueTask
Task<int> task = GetTaskAsync();
ValueTask<int> valueTask = new ValueTask<int>(task);
return await valueTask;
}
private Task<int> GetTaskAsync() => Task.FromResult(42);
// ValueTask 最佳实践
public async Task ValueTaskUsageAsync()
{
// 好:立即 await
string value1 = await GetValueAsync("key1");
// 坏:存储 ValueTask
// ValueTask<string> vt = GetValueAsync("key2");
// await vt; // 第一次 await
// await vt; // 第二次 await - 错误!
// 好:如果需要多次使用,转换为 Task
Task<string> task = GetValueAsync("key2").AsTask();
await task;
await task; // 使用 Task 没问题
}
// ConfigureAwait 与 ValueTask
public async ValueTask ConfigureAwaitExample()
{
// 不捕获上下文(用于库代码)
string value = await GetValueAsync("key")
.ConfigureAwait(false);
Console.WriteLine(value);
}
}
ConfigureAwait
ConfigureAwait 控制是否捕获同步上下文,对库代码和避免死锁至关重要。
using System;
using System.Threading.Tasks;
public class ConfigureAwaitExamples
{
// 库方法 - 使用 ConfigureAwait(false)
public async Task<string> LibraryMethodAsync()
{
// 不捕获同步上下文
await Task.Delay(100).ConfigureAwait(false);
// 继续在线程池线程上
string result = await GetDataAsync()
.ConfigureAwait(false);
return result.ToUpper();
}
// UI 方法 - 使用默认(或 ConfigureAwait(true))
public async Task UpdateUIAsync()
{
string data = await LoadDataAsync();
// 继续在 UI 线程上
// 可以安全更新 UI 控件
Console.WriteLine($"数据: {data}");
}
// 避免死锁
public class DeadlockExample
{
// 这可能在同步上下文中导致死锁
public string BadSync()
{
// 不要这样做
return GetDataAsync().Result; // 死锁!
}
// 使用 ConfigureAwait(false) 修复
public string GoodSync()
{
return GetDataAsync()
.ConfigureAwait(false)
.GetAwaiter()
.GetResult();
}
// 更好:使其异步
public async Task<string> BestAsync()
{
return await GetDataAsync();
}
}
// 混合上下文
public async Task MixedContextAsync()
{
// 在捕获的上下文上运行
await Task.Delay(100);
Console.WriteLine("在原始上下文上");
// 在线程池上运行
await Task.Delay(100).ConfigureAwait(false);
Console.WriteLine("在线程池上");
// 仍然在线程池上(ConfigureAwait 效果持续)
await Task.Delay(100);
Console.WriteLine("仍然在线程池上");
}
private async Task<string> GetDataAsync()
{
await Task.Delay(100);
return "数据";
}
private async Task<string> LoadDataAsync()
{
await Task.Delay(100);
return "加载的数据";
}
}
异步流 (IAsyncEnumerable)
异步流允许对数据序列进行异步迭代,非常适合流式 API 和大型数据集。
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
public class AsyncStreamExamples
{
// 基本异步流
public async IAsyncEnumerable<int> GenerateNumbersAsync(
int count)
{
for (int i = 0; i < count; i++)
{
await Task.Delay(100);
yield return i;
}
}
// 消费异步流
public async Task ConsumeStreamAsync()
{
await foreach (int number in GenerateNumbersAsync(10))
{
Console.WriteLine(number);
}
}
// 带有取消的异步流
public async IAsyncEnumerable<string> ReadLinesAsync(
string filePath,
[EnumeratorCancellation] CancellationToken cancellationToken =
default)
{
using var reader = new System.IO.StreamReader(filePath);
while (!reader.EndOfStream)
{
cancellationToken.ThrowIfCancellationRequested();
string? line = await reader.ReadLineAsync();
if (line != null)
{
yield return line;
}
}
}
// 过滤异步流
public async IAsyncEnumerable<int> FilterEvenNumbersAsync(
IAsyncEnumerable<int> source)
{
await foreach (int number in source)
{
if (number % 2 == 0)
{
yield return number;
}
}
}
// 转换异步流
public async IAsyncEnumerable<string> FormatNumbersAsync(
IAsyncEnumerable<int> source)
{
await foreach (int number in source)
{
yield return $"数字: {number:D3}";
}
}
// 从 API 获取异步流
public async IAsyncEnumerable<string> FetchPagesAsync(
string baseUrl,
int totalPages)
{
using var client = new HttpClient();
for (int page = 1; page <= totalPages; page++)
{
string url = $"{baseUrl}?page={page}";
string content = await client.GetStringAsync(url);
yield return content;
}
}
// 组合异步流
public async Task ComposeStreamsAsync()
{
var numbers = GenerateNumbersAsync(20);
var evens = FilterEvenNumbersAsync(numbers);
var formatted = FormatNumbersAsync(evens);
await foreach (string value in formatted)
{
Console.WriteLine(value);
}
}
}
并行异步操作
结合并行性和异步操作以实现最大吞吐量。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
public class ParallelAsyncExamples
{
// 并行处理项
public async Task<List<string>> ProcessInParallelAsync(
List<int> items)
{
var tasks = items.Select(async item =>
{
await Task.Delay(100);
return $"已处理 {item}";
});
string[] results = await Task.WhenAll(tasks);
return results.ToList();
}
// 节流并行执行
public async Task<List<string>> ThrottledParallelAsync(
List<int> items,
int maxConcurrency)
{
var semaphore = new SemaphoreSlim(maxConcurrency);
var tasks = items.Select(async item =>
{
await semaphore.WaitAsync();
try
{
await Task.Delay(100);
return $"已处理 {item}";
}
finally
{
semaphore.Release();
}
});
string[] results = await Task.WhenAll(tasks);
return results.ToList();
}
// Parallel.ForEachAsync (.NET 6+)
public async Task ParallelForEachAsyncExample(List<int> items)
{
await Parallel.ForEachAsync(
items,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
async (item, cancellationToken) =>
{
await Task.Delay(100, cancellationToken);
Console.WriteLine($"已处理 {item}");
});
}
// 批处理
public async Task<List<string>> BatchProcessAsync(
List<int> items,
int batchSize)
{
var results = new List<string>();
for (int i = 0; i < items.Count; i += batchSize)
{
var batch = items.Skip(i).Take(batchSize);
var batchResults = await ProcessInParallelAsync(
batch.ToList());
results.AddRange(batchResults);
}
return results;
}
}
异步代码中的错误处理
适当的错误处理对于健壮的异步应用程序至关重要。
using System;
using System.Threading.Tasks;
public class AsyncErrorHandling
{
// 基本 try-catch
public async Task<string> BasicErrorHandlingAsync()
{
try
{
return await RiskyOperationAsync();
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"操作错误: {ex.Message}");
return "默认";
}
catch (Exception ex)
{
Console.WriteLine($"意外错误: {ex.Message}");
throw;
}
}
// 来自 WhenAll 的 AggregateException
public async Task HandleMultipleErrorsAsync()
{
try
{
await Task.WhenAll(
FailingTaskAsync("任务 1"),
FailingTaskAsync("任务 2"),
FailingTaskAsync("任务 3")
);
}
catch (Exception ex)
{
// 只捕获第一个异常
Console.WriteLine($"第一个错误: {ex.Message}");
}
}
// 处理所有异常
public async Task HandleAllErrorsAsync()
{
var tasks = new[]
{
FailingTaskAsync("任务 1"),
FailingTaskAsync("任务 2"),
FailingTaskAsync("任务 3")
};
try
{
await Task.WhenAll(tasks);
}
catch
{
// 迭代所有任务以查看所有异常
foreach (var task in tasks)
{
if (task.IsFaulted && task.Exception != null)
{
foreach (var ex in task.Exception.InnerExceptions)
{
Console.WriteLine($"错误: {ex.Message}");
}
}
}
}
}
// finally 块按预期工作
public async Task FinallyBlockAsync()
{
try
{
await RiskyOperationAsync();
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
}
finally
{
// 总是执行
Console.WriteLine("清理代码");
}
}
private async Task<string> RiskyOperationAsync()
{
await Task.Delay(100);
throw new InvalidOperationException("出了点问题");
}
private async Task FailingTaskAsync(string name)
{
await Task.Delay(100);
throw new InvalidOperationException($"{name} 失败");
}
}
最佳实践
- 全程使用
async/await- 避免混合异步和同步代码 - 对于 CPU 绑定工作使用
Task.Run,对于 I/O 使用原生异步 API - 对于经常同步完成的热路径使用
ValueTask<T> - 在库代码中始终使用
ConfigureAwait(false) - 永远不要在 Tasks 上使用
.Result或.Wait()- 会导致死锁 - 使用
CancellationToken正确处理取消 - 使用
Task.WhenAll进行并行操作,而不是顺序 await - 对异步操作实现适当的异常处理
- 对于异步产生的序列使用异步流
- 避免
async void,除了事件处理程序
常见陷阱
- 使用
.Result或.Wait()阻塞异步代码导致死锁 - 在库代码中不使用
ConfigureAwait(false),不必要地捕获上下文 - 使用
async void方法,这些方法无法正确等待或捕获异常 - 忘记 await 任务,导致发射后不管行为
- 没有正确处理并行任务中的异常
- 过度并行化,有太多并发操作
- 对已经是异步的 I/O 操作使用
Task.Run(双重包装) - 不通过异步调用链传递
CancellationToken - 存储和多次 await
ValueTask - 在异步 lambda 闭包中捕获大对象导致内存问题
何时使用异步模式
在需要时使用异步模式:
- 响应式 UI 应用程序,在 I/O 操作期间不冻结
- Web API 和服务,高效处理许多并发请求
- 不应该阻塞线程的数据库操作
- 用于读取和写入大文件的文件 I/O 操作
- 包括 HTTP 请求和套接字通信的网络操作
- 流式大型数据集而不将所有内容加载到内存中
- 使用
Task.Run卸载到线程池的 CPU 绑定工作 - 具有适当错误处理的可组合异步操作
- 可取消的长时间运行操作
- 服务器应用程序中的最大可扩展性