文章目录
一、引言 ?二、异步编程的基本概念三、Async 和 Await 的基本用法四、Async 方法的转换过程五、Await 的工作原理六、异步方法的执行流程七、异常处理八、避免常见陷阱8.1 死锁8.2 忘记await8.3 过度使用 async void 九、高级模式9.1 并行执行多个任务9.2 带超时的异步操作 十、结论十一、代码之美系列目录???
一、引言 ?
?在C#中, async 和 await 关键字是用于实现异步编程的强大工具。它们的引入极大地 简化 了 异步 代码的编写,使得开发人员能够更容易地创建响应式和高性能的应用程序。但是,要真正理解它们的工作原理,我们需要深入探讨它们在底层到底在做什么。
二、异步编程的基本概念
在深入async 和 await 之前,我们需要理解一些基本概念:
三、Async 和 Await 的基本用法
让我们从一个简单的例子开始:
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; }} 在这个例子中:
async关键字标记方法为异步方法。方法返回 Task ,表示一个最终会产生 string 的异步操作。await 用于等待 GetStringAsync 方法完成,而不阻塞线程。 四、Async 方法的转换过程
当你使用 async 关键字标记一个方法时,编译器会将其转换为一个 状态机 。这个过程大致如下:
IAsyncStateMachine 接口的结构体。将方法体转换为状态机的 MoveNext 方法。每个 await 表达式都成为一个可能 的暂停点 ,对应状态机中的一个状态。 async 方法如何被分解为多个步骤,每个 await 表达式对应一个状态。
五、Await 的工作原理
await 关键字的主要作用是:
awaited 任务是否已完成。如果已完成,继续执行后续代码。如果未完成,注册一个回调并返回控制权给调用者。 让我们通过一个例子来详细说明:
public async Task DoWorkAsync(){ Console.WriteLine("开始工作"); await Task.Delay(1000); // 模拟耗时操作 Console.WriteLine("工作完成");} 当执行到 await Task.Delay(1000) 时:
Task.Delay(1000) 是否已完成。如果未完成: 创建一个 continuation (后续操作),包含 await 之后的代码。将这个 continuation 注册到Task上。返回控制权给调用者。 当 Task.Delay(1000) 完成时: 触发注册的 continuation 。恢复执行 await 之后的代码。 六、异步方法的执行流程
让我们通过一个更复杂的例子来理解 异步方法 的执行流程:
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结束");} 
执行流程如下:
MainMethodAsync 开始执行,打印"1. 开始主方法"。遇到 await Method1Async(),进入Method1Async。 Method1Async 打印"2. 开始方法1"。遇到 await Task.Delay(1000) ,注册 continuation 并返回。控制权回到 MainMethodAsync,但因为 Method1Async 未完成,所以 MainMethodAsync 也返回。1秒后 ,Task.Delay 完成,触发 continuation。Method1Async 继续执行,打印"3. 方法1结束"。Method1Async 完成,触发 MainMethodAsync 的 continuation。MainMethodAsync 继续执行,打印"4. 主方法结束"。 七、异常处理
async/await 模式下的异常处理非常直观。你可以使用常规的 try/catch 块,异步方法中抛出的异常会被封装在返回的 Task 中,并在 await 时重新抛出。
八、避免常见陷阱
使用 async/await 时,有一些常见的陷阱需要注意:
8.1 死锁
考虑如下代码:
public async Task DeadlockDemoAsync(){ await Task.Delay(1000).ConfigureAwait(false);}public void CallAsyncMethod(){ DeadlockDemoAsync().Wait(); // 可能导致死锁} 当在 UI线程(或任何有同步上下文的线程)中调用 CallAsyncMethod() 时,会发生死锁。Wait() 方法会阻塞当前线程,等待异步操作完成。当异步操作完成时,它默认会尝试在原始的同步上下文(通常是UI线程)上继续执行。但是原始线程已经被 Wait() 阻塞了,导致死锁。 8.2 忘记await
public async Task ForgetAwaitDemoAsync(){ DoSomethingAsync(); // 忘记await Console.WriteLine("完成"); // 这行可能在异步操作完成之前执行} 8.3 过度使用 async void
除了事件处理程序外,应避免使用 async void 方法,因为它们的异常难以捕获和处理。
public async void BadAsyncVoidMethod(){ await Task.Delay(1000); throw new Exception("这个异常很难被捕获");} 九、高级模式
9.1 并行执行多个任务
使用 Task.WhenAll 可以并行执行多个异步任务:
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} 完成");} 
9.2 带超时的异步操作
使用 Task.WhenAny 和 Task.Delay 可以实现带超时的异步操作:
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; }} 十、结论
async和 await 极大地简化了 C# 中的异步编程,使得编写高效、响应式的应用程序变得更加容易。通过将复杂的 异步操作 转换为看似 同步 的代码,它们提高了代码的 可读性 和 可维护性。
十一、代码之美系列目录???
一、C# 命名规则规范
二、C# 代码约定规范
三、C# 参数类型约束
四、浅析 B/S 应用程序体系结构原则
五、浅析 C# Async 和 Await
六、浅析 ASP.NET Core SignalR 双工通信
七、浅析 ASP.NET Core 和 MongoDB 创建 Web API
八、浅析 ASP.NET Web UI 框架 Razor Pages/MVC/Web API/Blazor
九、如何使用 MiniProfiler WebAPI 分析工具
十、浅析 .NET Core 中各种 Filter
十一、C#.Net筑基-类型系统
十二、C#.Net 筑基-运算符
十三、C#.Net筑基-解密委托与事件
十四、C#.Net筑基-集合知识大全
十五、C#.Net筑基 - 常见类型
十六、C#.NET体系图文概述—2024最全总结
