C# 异步编程笔记
1. Task.Delay().GetAwaiter() 的含义
Task.Delay(100).GetAwaiter()
是一种不使用 await
关键字但仍能获取异步操作结果的方式。
// 使用 GetAwaiter() 的方式
var awaiter = Task.Delay(100).GetAwaiter();
awaiter.OnCompleted(() => {
Console.WriteLine("延迟完成");
});
// 等效的 await 方式
await Task.Delay(100);
Console.WriteLine("延迟完成");
关键点:
GetAwaiter()
返回一个TaskAwaiter
对象可以通过
OnCompleted()
方法注册回调函数主要用于底层框架开发,日常开发建议使用
await
2. 直接调用异步方法 vs await 调用的区别
直接调用异步方法
public async Task DirectCallExample()
{
// 直接调用 - 返回 Task 对象,不等待完成
Task task = SomeAsyncMethod();
// 继续执行其他代码
Console.WriteLine("这行代码会立即执行");
// 稍后等待结果
await task;
}
使用 await 调用
public async Task AwaitCallExample()
{
// 使用 await - 等待方法完成后再继续
await SomeAsyncMethod();
// 只有上面的方法完成后才会执行这行
Console.WriteLine("这行代码会在异步方法完成后执行");
}
主要区别:
直接调用:立即返回 Task 对象,不阻塞当前线程
await 调用:等待操作完成,然后继续执行后续代码
异常处理:await 会自动解包异常,直接调用需要手动处理
3. GetAwaiter() 方法详解
public async Task GetAwaiterExample()
{
Task task = Task.Delay(1000);
// GetAwaiter() 是实例方法,不是静态方法
TaskAwaiter awaiter = task.GetAwaiter();
// 检查任务状态
Console.WriteLine($"任务是否完成: {awaiter.IsCompleted}");
// 注册完成回调
awaiter.OnCompleted(() => {
Console.WriteLine("任务完成回调");
});
// 获取结果(会阻塞直到完成)
awaiter.GetResult();
}
注意: GetAwaiter()
是 Task 的实例方法,不是静态方法。
4. 线程间异常处理
异步方法中的异常不会自动传播到调用线程,需要正确处理:
public async Task ExceptionHandlingExample()
{
try
{
// 错误的方式 - 异常不会被捕获
Task.Run(() => throw new Exception("后台线程异常"));
// 正确的方式 - 使用 await 捕获异常
await Task.Run(() => throw new Exception("后台线程异常"));
}
catch (Exception ex)
{
Console.WriteLine($"捕获到异常: {ex.Message}");
}
}
// 处理多个任务的异常
public async Task HandleMultipleTaskExceptions()
{
var tasks = new[]
{
Task.Run(() => throw new Exception("任务1异常")),
Task.Run(() => throw new Exception("任务2异常")),
Task.Run(() => Console.WriteLine("任务3正常"))
};
try
{
await Task.WhenAll(tasks);
}
catch (Exception ex)
{
// 只能捕获第一个异常
Console.WriteLine($"捕获到异常: {ex.Message}");
// 要获取所有异常,需要检查任务状态
foreach (var task in tasks.Where(t => t.IsFaulted))
{
Console.WriteLine($"任务异常: {task.Exception?.InnerException?.Message}");
}
}
}
5. 异步方法的同步上下文
public async Task SynchronizationContextExample()
{
Console.WriteLine($"主线程ID: {Thread.CurrentThread.ManagedThreadId}");
// 在默认情况下,await 后会回到原始同步上下文
await Task.Delay(100);
Console.WriteLine($"await 后线程ID: {Thread.CurrentThread.ManagedThreadId}");
// 使用 ConfigureAwait(false) 不回到原始上下文
await Task.Delay(100).ConfigureAwait(false);
Console.WriteLine($"ConfigureAwait(false) 后线程ID: {Thread.CurrentThread.ManagedThreadId}");
// 使用 Task.Run 切换到线程池线程
await Task.Run(async () =>
{
Console.WriteLine($"Task.Run 内线程ID: {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(100);
Console.WriteLine($"Task.Run 内 await 后线程ID: {Thread.CurrentThread.ManagedThreadId}");
});
Console.WriteLine($"Task.Run 后线程ID: {Thread.CurrentThread.ManagedThreadId}");
}
6. Task.WhenAll 使用案例
Task.WhenAll
用于并发执行多个异步操作并等待它们全部完成:
public async Task TaskWhenAllExample()
{
// 案例1: 并发执行多个独立任务
var task1 = DownloadDataAsync("url1");
var task2 = DownloadDataAsync("url2");
var task3 = DownloadDataAsync("url3");
// 等待所有任务完成
string[] results = await Task.WhenAll(task1, task2, task3);
Console.WriteLine("所有下载完成:");
for (int i = 0; i < results.Length; i++)
{
Console.WriteLine($"结果 {i + 1}: {results[i]}");
}
}
// 实际的下载方法示例
private async Task<string> DownloadDataAsync(string url)
{
using var client = new HttpClient();
await Task.Delay(Random.Shared.Next(1000, 3000)); // 模拟网络延迟
return $"来自 {url} 的数据";
}
// 案例2: 批量处理数据
public async Task BatchProcessExample()
{
var items = Enumerable.Range(1, 10);
// 并发处理所有项目
var tasks = items.Select(ProcessItemAsync);
var results = await Task.WhenAll(tasks);
Console.WriteLine($"处理了 {results.Length} 个项目");
}
private async Task<string> ProcessItemAsync(int item)
{
await Task.Delay(100 * item); // 模拟处理时间
return $"处理完成项目 {item}";
}
// 案例3: 设置超时的并发操作
public async Task TaskWhenAllWithTimeoutExample()
{
var tasks = new[]
{
SlowOperationAsync("操作1", 2000),
SlowOperationAsync("操作2", 1500),
SlowOperationAsync("操作3", 3000)
};
try
{
// 设置5秒超时
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var timeoutTask = Task.Delay(Timeout.Infinite, cts.Token);
var completedTask = await Task.WhenAny(Task.WhenAll(tasks), timeoutTask);
if (completedTask == timeoutTask)
{
Console.WriteLine("操作超时");
}
else
{
var results = await Task.WhenAll(tasks);
Console.WriteLine("所有操作完成");
}
}
catch (OperationCanceledException)
{
Console.WriteLine("操作被取消");
}
}
private async Task<string> SlowOperationAsync(string name, int delayMs)
{
await Task.Delay(delayMs);
return $"{name} 完成";
}
7. CancellationTokenSource 使用案例
CancellationTokenSource
用于取消异步操作:
public async Task CancellationTokenExample()
{
// 案例1: 基本取消操作
using var cts = new CancellationTokenSource();
// 5秒后自动取消
cts.CancelAfter(TimeSpan.FromSeconds(5));
try
{
await LongRunningOperationAsync(cts.Token);
Console.WriteLine("操作完成");
}
catch (OperationCanceledException)
{
Console.WriteLine("操作被取消");
}
}
private async Task LongRunningOperationAsync(CancellationToken cancellationToken)
{
for (int i = 0; i < 100; i++)
{
// 检查取消请求
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(100, cancellationToken);
Console.WriteLine($"处理进度: {i + 1}/100");
}
}
// 案例2: 用户交互取消
public async Task UserInteractiveCancellationExample()
{
using var cts = new CancellationTokenSource();
// 启动后台任务
var backgroundTask = BackgroundWorkAsync(cts.Token);
// 模拟用户按键取消
Console.WriteLine("按任意键取消操作...");
Console.ReadKey();
cts.Cancel();
try
{
await backgroundTask;
}
catch (OperationCanceledException)
{
Console.WriteLine("用户取消了操作");
}
}
private async Task BackgroundWorkAsync(CancellationToken cancellationToken)
{
int counter = 0;
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine($"后台工作中... {++counter}");
await Task.Delay(1000, cancellationToken);
}
}
// 案例3: 链接多个取消令牌
public async Task LinkedCancellationTokenExample()
{
using var appCts = new CancellationTokenSource();
using var userCts = new CancellationTokenSource();
// 创建链接的取消令牌
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(
appCts.Token, userCts.Token);
// 任何一个取消都会取消操作
var task = SomeOperationAsync(linkedCts.Token);
// 模拟应用程序关闭
_ = Task.Run(async () =>
{
await Task.Delay(3000);
appCts.Cancel();
});
// 模拟用户取消
_ = Task.Run(async () =>
{
await Task.Delay(2000);
userCts.Cancel();
});
try
{
await task;
}
catch (OperationCanceledException)
{
Console.WriteLine("操作被取消");
}
}
private async Task SomeOperationAsync(CancellationToken cancellationToken)
{
for (int i = 0; i < 50; i++)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.Delay(100, cancellationToken);
Console.WriteLine($"操作进度: {i + 1}/50");
}
}
// 案例4: 优雅关闭模式
public class GracefulShutdownService
{
private readonly CancellationTokenSource _shutdownCts = new();
public CancellationToken ShutdownToken => _shutdownCts.Token;
public async Task StartAsync()
{
var tasks = new[]
{
WorkerTask1(_shutdownCts.Token),
WorkerTask2(_shutdownCts.Token),
WorkerTask3(_shutdownCts.Token)
};
try
{
await Task.WhenAll(tasks);
}
catch (OperationCanceledException)
{
Console.WriteLine("服务正在关闭...");
}
}
public void Shutdown()
{
_shutdownCts.Cancel();
}
private async Task WorkerTask1(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Worker 1 正在工作");
await Task.Delay(1000, cancellationToken);
}
}
private async Task WorkerTask2(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Worker 2 正在工作");
await Task.Delay(1500, cancellationToken);
}
}
private async Task WorkerTask3(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
Console.WriteLine("Worker 3 正在工作");
await Task.Delay(2000, cancellationToken);
}
}
public void Dispose()
{
_shutdownCts?.Dispose();
}
}
最佳实践总结
优先使用
await
而不是GetAwaiter()
,除非在底层框架开发中正确处理异常:使用
await
来捕获异步操作中的异常注意同步上下文:在库代码中使用
ConfigureAwait(false)
避免死锁使用
Task.WhenAll
进行并发操作以提高性能合理使用
CancellationToken
使异步操作可取消避免混合同步和异步代码,保持一致的异步模式