C# 异步编程实现方式

70

异步编程的作用

提高性能和资源利用率:

异步编程可以在等待 I/O 操作完成的同时释放线程资源,使得线程能够继续执行其他任务,从而提高了系统的资源利用率和性能。

改善用户体验:

通过异步编程,可以避免在等待长时间操作完成时出现界面卡顿或无响应的情况,从而改善用户体验,使应用程序更加流畅和响应。

简化编程模型:

使用 C# 提供的async 和 await 关键字可以使异步编程变得更加简洁和易于理解,避免了传统的回调地狱(callback hell),使代码更具可读性和可维护性。

提高并发性:

通过异步编程,可以更有效地处理并发请求,从而提高系统的并发性能,使得应用程序能够更好地处理大量用户请求。

支持大规模并行编程:异步编程模型使得在大规模并行编程中更容易管理和控制异步任务的执行,提供了更灵活的并发编程方式。

异步方法的基本结构

  • async 关键字:修饰方法,表示该方法包含异步操作。

  • 返回值:必须是 Task、Task<T> 或 void(void 仅用于事件处理,不推荐)。

  • await 关键字:用于等待异步操作完成,期间释放当前线程,避免阻塞。

public async Task DoSomethingAsync()
{
    // 同步代码
    Console.WriteLine("Start");

    // 异步等待(不阻塞线程)
    await Task.Delay(1000); // 模拟耗时操作(如I/O、网络请求)

    // 后续代码在异步操作完成后自动恢复执行
    Console.WriteLine("End");
}

异步方法的调用

使用 await 调用异步方法

await DoSomethingAsync();

果不使用 await,返回的 Task 对象表示异步操作的状态

Task task = DoSomethingAsync();
// 可以在此执行其他操作
await task; // 等待任务完成

异步返回值类型

Task 无返回值的异步操作。

public async Task ProcessDataAsync()
{
    await Task.Delay(1000);
}

Task<T> 返回类型为 T 的结果

public async Task<int> CalculateSumAsync(int a, int b)
{
    await Task.Delay(1000);
    return a + b;
}

// 调用
int result = await CalculateSumAsync(3, 5);

void 仅用于事件处理(如按钮点击事件),不推荐其他场景使用(异常无法捕获)

private async void Button_Click(object sender, EventArgs e)
{
    await Task.Delay(1000);
    MessageBox.Show("Done!");
}

底层实现原理

状态机:编译器将 async 方法转换为一个状态机,跟踪 await 的位置和上下文。

线程池:异步操作由线程池中的线程执行,避免阻塞主线程(如UI线程)。

上下文同步:默认情况下,await 完成后会尝试回到原始上下文(如UI线程),可通过 ConfigureAwait(false) 禁用

异步操作的启动

使用 Task.Run

将同步代码包装为异步任务(适用于CPU密集型操作)

public async Task ProcessHeavyWorkAsync()
{
    await Task.Run(() =>
    {
        // CPU密集型操作(如复杂计算)
        for (int i = 0; i < 1000000; i++) { /* ... */ }
    });
}

使用 异步 API

使用原生支持异步的API(如文件I/O、网络请求)

public async Task<string> ReadFileAsync(string path)
{
    using (StreamReader reader = new StreamReader(path))
    {
        return await reader.ReadToEndAsync();
    }
}

组合多个异步操作

Task.WhenAll:等待多个任务全部完成

public async Task ProcessMultipleTasksAsync()
{
    Task task1 = DoWorkAsync();
    Task task2 = DoAnotherWorkAsync();
    await Task.WhenAll(task1, task2);
}

Task.WhenAny:等待任意一个任务完成

public async Task<int> GetFirstResultAsync()
{
    Task<int> task1 = FetchDataFromSource1Async();
    Task<int> task2 = FetchDataFromSource2Async();
    Task<int> completedTask = await Task.WhenAny(task1, task2);
    return await completedTask;
}

取消异步操作

使用 CancellationToken

public async Task LongRunningOperationAsync(CancellationToken cancellationToken)
{
    for (int i = 0; i < 100; i++)
    {
        cancellationToken.ThrowIfCancellationRequested();
        await Task.Delay(1000, cancellationToken);
    }
}

// 调用示例
var cts = new CancellationTokenSource();
var task = LongRunningOperationAsync(cts.Token);
// 5秒后取消
cts.CancelAfter(5000); 

常见误区

避免 async void:除非是事件处理,否则使用 async Task。

不要阻塞异步代码:避免使用 .Result 或 .Wait(),优先使用 await。

区分CPU密集和I/O密集:CPU密集操作用 Task.Run,I/O密集用原生异步API。