编辑
2025-09-19
C#
00

目录

引言
异步编程的基本概念
Async 和 Await 的基本用法
Async 方法的转换过程
Await 的工作原理
异步方法的执行流程
异常处理
避免常见陷阱
死锁
忘记await
过度使用async void
高级模式
并行执行多个任务
带超时的异步操作
结论

引言

在C#中,asyncawait关键字是用于实现异步编程的强大工具。它们的引入极大地简化了异步代码的编写,使得开发人员能够更容易地创建响应式和高性能的应用程序。但是,要真正理解它们的工作原理,我们需要深入探讨它们在底层到底在做什么。

异步编程的基本概念

在深入asyncawait之前,我们需要理解一些基本概念:

  • 同步执行: 代码按顺序执行,每个操作完成后才会进行下一个。
  • 异步执行: 允许长时间运行的操作在后台进行,而不阻塞主线程。
  • 任务(Task): 表示一个异步操作。
  • 线程: 程序执行的最小单位。

Async 和 Await 的基本用法

让我们从一个简单的例子开始:

C#
static async Task Main(string[] args) { var context = await GetWebContentAsync("http://www.baidu.com"); Console.WriteLine(context); } public static async Task<string> GetWebContentAsync(string url) { using (var client = new HttpClient()) { string content = await client.GetStringAsync(url); return content; } }

image.png

在这个例子中:

  • async关键字标记方法为异步方法。
  • 方法返回Task<string>,表示一个最终会产生string的异步操作。
  • await用于等待GetStringAsync方法完成,而不阻塞线程。

Async 方法的转换过程

当你使用async关键字标记一个方法时,编译器会将其转换为一个状态机。这个过程大致如下:

  1. 创建一个实现了IAsyncStateMachine接口的结构体。
  2. 将方法体转换为状态机的MoveNext方法。
  3. 每个await表达式都成为一个可能的暂停点,对应状态机中的一个状态。

async方法如何被分解为多个步骤,每个await表达式对应一个状态。

Await 的工作原理

await关键字的主要作用是:

  1. 检查awaited任务是否已完成。
  2. 如果已完成,继续执行后续代码。
  3. 如果未完成,注册一个回调并返回控制权给调用者。

让我们通过一个例子来详细说明:

C#
public async Task DoWorkAsync() { Console.WriteLine("开始工作"); await Task.Delay(1000); // 模拟耗时操作 Console.WriteLine("工作完成"); }

当执行到await Task.Delay(1000)时:

  1. 检查Task.Delay(1000)是否已完成。
  2. 如果未完成:
    • 创建一个continuation(后续操作),包含await之后的代码。
    • 将这个continuation注册到Task上。
    • 返回控制权给调用者。
  3. Task.Delay(1000)完成时:
    • 触发注册的continuation。
    • 恢复执行await之后的代码。

异步方法的执行流程

让我们通过一个更复杂的例子来理解异步方法的执行流程:

C#
static async Task Main(string[] args) { await MainMethodAsync(); Console.ReadKey(); } public static async Task MainMethodAsync() { Console.WriteLine("1. 开始主方法"); await Method1Async(); Console.WriteLine("4. 主方法结束"); } public static async Task Method1Async() { Console.WriteLine("2. 开始方法1"); await Task.Delay(1000); Console.WriteLine("3. 方法1结束"); }

image.png

执行流程如下:

  1. MainMethodAsync开始执行,打印"1. 开始主方法"。
  2. 遇到await Method1Async(),进入Method1Async
  3. Method1Async打印"2. 开始方法1"。
  4. 遇到await Task.Delay(1000),注册continuation并返回。
  5. 控制权回到MainMethodAsync,但因为Method1Async未完成,所以MainMethodAsync也返回。
  6. 1秒后,Task.Delay完成,触发continuation。
  7. Method1Async继续执行,打印"3. 方法1结束"。
  8. Method1Async完成,触发MainMethodAsync的continuation。
  9. MainMethodAsync继续执行,打印"4. 主方法结束"。

异常处理

async/await模式下的异常处理非常直观。你可以使用常规的try/catch块,异步方法中抛出的异常会被封装在返回的Task中,并在await时重新抛出。

避免常见陷阱

使用async/await时,有一些常见的陷阱需要注意:

死锁

考虑以下代码:

C#
public async Task DeadlockDemoAsync() { await Task.Delay(1000).ConfigureAwait(false); } public void CallAsyncMethod() { DeadlockDemoAsync().Wait(); // 可能导致死锁 }
  • 当在UI线程(或任何有同步上下文的线程)中调用CallAsyncMethod()时,会发生死锁。
  • Wait()方法会阻塞当前线程,等待异步操作完成。
  • 当异步操作完成时,它默认会尝试在原始的同步上下文(通常是UI线程)上继续执行。
  • 但是原始线程已经被Wait()阻塞了,导致死锁。

忘记await

C#
public async Task ForgetAwaitDemoAsync() { DoSomethingAsync(); // 忘记await Console.WriteLine("完成"); // 这行可能在异步操作完成之前执行 }

始终记得在异步方法调用前使用await

过度使用async void

除了事件处理程序外,应避免使用async void方法,因为它们的异常难以捕获和处理。

C#
public async void BadAsyncVoidMethod() { await Task.Delay(1000); throw new Exception("这个异常很难被捕获"); }

高级模式

并行执行多个任务

使用Task.WhenAll可以并行执行多个异步任务:

C#
public async Task ParallelExecutionDemo() { var task1 = DoWorkAsync(1); var task2 = DoWorkAsync(2); var task3 = DoWorkAsync(3); await Task.WhenAll(task1, task2, task3); Console.WriteLine("所有任务完成"); } public async Task DoWorkAsync(int id) { await Task.Delay(1000); Console.WriteLine($"任务 {id} 完成"); }

image.png

带超时的异步操作

使用Task.WhenAnyTask.Delay可以实现带超时的异步操作:

C#
static async Task Main(string[] args) { await FetchDataWithTimeoutAsync("http://www.google.com",new TimeSpan(0, 0, 3)); Console.ReadKey(); } static async Task<string> FetchDataWithTimeoutAsync(string url, TimeSpan timeout) { using (var client = new HttpClient()) { var dataTask = client.GetStringAsync(url); var timeoutTask = Task.Delay(timeout); var completedTask = await Task.WhenAny(dataTask, timeoutTask); if (completedTask == timeoutTask) { throw new TimeoutException("操作超时"); } return await dataTask; } }

结论

asyncawait极大地简化了C#中的异步编程,使得编写高效、响应式的应用程序变得更加容易。通过将复杂的异步操作转换为看似同步的代码,它们提高了代码的可读性和可维护性。

然而,要充分利用这些特性,我们需要深入理解它们的工作原理,包括状态机的概念、执行流程、异常处理以及常见陷阱。通过掌握这些知识,我们可以编写出更加健壮和高效的异步代码。

记住,异步编程是一个强大的工具,但它也带来了额外的复杂性。在使用asyncawait时,始终考虑性能影响和潜在的并发问题。通过持续学习和实践,你将能够充分发挥C#异步编程的潜力。

本文作者:技术老小子

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!