Decade
Decade
Published on 2025-07-24 / 20 Visits
0
0

新异步方法

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();
    }
}

最佳实践总结

  1. 优先使用 await 而不是 GetAwaiter(),除非在底层框架开发中

  2. 正确处理异常:使用 await 来捕获异步操作中的异常

  3. 注意同步上下文:在库代码中使用 ConfigureAwait(false) 避免死锁

  4. 使用 Task.WhenAll 进行并发操作以提高性能

  5. 合理使用 CancellationToken 使异步操作可取消

  6. 避免混合同步和异步代码,保持一致的异步模式


Comment