文章目录
一、引言 ?二、异步编程的基本概念三、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最全总结