async await正确使用方法

本文介绍了ES7中新增的异步编程方法async/await的基本概念及其使用方式。async用于定义一个异步函数,而await则用于等待异步操作的完成。文章通过示例详细解释了如何在函数中使用await来调用返回Promise的方法。

        async await是es7新增的一个异步方法,async是在一个函数前添加,而await只能在async函数里才会执行。而await后面 必须跟一个new Promise的函数并返回他

        使用如下图

        test函数前加async,里边就可以使用await调用方法, 调用方法必须有一个 return new Promise,而promise的 resolve成功响应返回的值就是await取到的值。

 

<think>我们正在讨论C#中的asyncawait的工作原理。根据之前的对话历史和引用的资料,用户希望了解其使用原理。 引用[1]提到async/await简化了异步编程,但需要深入理解其底层机制;引用[2]提到控制流如何在异步方法之间移动;引用[3]展示了一个例子,说明await如何释放UI线程并在操作完成后回到原上下文执行。 核心原理: 1. async关键字标记的方法会被编译器重写为一个状态机。 2. await操作符会检查等待的操作是否完成。如果完成,则同步继续执行;否则,将方法挂起,返回控制权给用者,并注册一个续体(continuation)以便在操作完成后恢复执行。 3. 当异步操作完成时,续体将被度执行(可能是回到原始上下文,如UI线程,也可能在线程池线程中,取决于配置)。 状态机的工作流程: - 编译器将async方法拆分成多个片段,每个await点都是一个潜在的分割点。 - 状态机维护当前执行的位置(状态)以及局部变量等上下文。 执行流程示例(引用[3]的例子): 用户点击按钮(UI线程) -> 执行DownloadButton_Click()(UI线程) -> 用DownloadFileAsync()(UI线程) -> 遇到第一个await(client.GetAsync),检查操作是否完成 如果未完成,则挂起方法,返回一个未完成的Task给用者(DownloadButton_Click) 同时,注册续体:当GetAsync完成时,继续执行DownloadFileAsync的后续代码(可能会回到UI线程,因为捕获了SynchronizationContext) -> UI线程被释放,可以响应其他操作 -> 后台线程完成网络请求 -> 触发续体:度继续执行(在UI线程上)DownloadFileAsyncawait之后的代码 -> 继续执行下一个await(循环内的ReadAsync/WriteAsync)同样处理 注意:await不会阻塞线程,它只是将方法挂起,让出当前线程。 我们还可以用状态图来表示: ```mermaid stateDiagram-v2 [*] --> 方法开始 方法开始 --> 遇到第一个await: 执行await之前的代码 遇到第一个await --> 检查是否完成 检查是否完成 --> 已完成: 是 检查是否完成 --> 未完成: 否 已完成 --> 继续执行后续代码 未完成 --> 挂起方法并返回Task 挂起方法并返回Task --> [*] 继续执行后续代码 --> 下一个await或结束 下一个await或结束 --> [*] 异步操作完成事件 --> 恢复执行 恢复执行 --> 继续执行后续代码 ``` 续体的度: 当异步操作完成时,续体(即await之后的代码)会被度执行。度方式取决于方法时的上下文: - 如果在UI线程上用,且没有显式配置(ConfigureAwait(true)),则续体会在UI线程上执行(通过SynchronizationContext.Post)。 - 如果配置了ConfigureAwait(false),则续体可能在线程池线程上执行,不保证回到原始上下文。 状态机的实现细节: 编译器会为async方法生成一个状态机结构(struct)或类(class,如果方法是非静态的或在lambda表达式中使用),其中: - 包含状态字段(记录当前执行到哪个位置) - 包含局部变量字段(保存方法的局部变量) - 包含一个TaskCompletionSource或类似的机制来返回Task - 实现IAsyncStateMachine接口(包含MoveNext和SetStateMachine方法) 在方法首次用时,状态机初始化状态(通常为-1),然后用MoveNext方法开始执行。当遇到await时,状态机记录位置(状态值),然后返回。当异步操作完成,它会用MoveNext方法,状态机从记录的状态值跳转到相应的位置继续执行。 示例代码(伪代码形式): ```csharp // 原始async方法 public async Task MyMethodAsync() { Console.WriteLine("Before first await"); await Task.Delay(1000); Console.WriteLine("After first await"); } // 编译器生成的状态机大致如下(简化): class MyMethodAsyncStateMachine : IAsyncStateMachine { public int state = -1; // 状态 public AsyncTaskMethodBuilder builder = AsyncTaskMethodBuilder.Create(); private TaskAwaiter awaiter; void MoveNext() { try { if (state == -1) { Console.WriteLine("Before first await"); awaiter = Task.Delay(1000).GetAwaiter(); if (!awaiter.IsCompleted) { state = 0; // 更新状态,表示下一次从第一个await之后开始 builder.AwaitUnsafeOnCompleted(ref awaiter, ref this); return; // 挂起,返回任务 } } // 状态0表示从第一个await之后恢复 if (state == 0) { // 这不再需要获取awaiter,因为已经有了 } awaiter.GetResult(); // 确保操作完成(可能重新抛出异常) Console.WriteLine("After first await"); builder.SetResult(); // 标记任务完成 } catch (Exception ex) { builder.SetException(ex); } } } ``` 注意:实际生成的代码比这复杂,但原理一致。 总结: async/await的实现依赖于编译器生成的状态机,该状态机在遇到await时挂起方法,并将后续代码包装为续体,在异步操作完成时度执行。整个过程不阻塞线程,提高了程序的响应性和资源利用率。 相关问题: 1. 如何在async方法正确处理异常? 2. 使用ConfigureAwait(false)有什么好处? 3. 在控制台应用程序和ASP.NET Core中使用async/await有什么不同? </think>### C# async/await 使用原理深度解析 async/await 是 C# 实现异步编程的核心机制,其底层运作基于**编译器生成的状态机模型**。以下是核心原理分步说明: #### 1. **编译器状态机转换原理** 🔄 当方法标记 `async` 时,编译器会将其重写为**状态机结构**(实现 `IAsyncStateMachine` 接口): ```csharp // 原始代码 public async Task DownloadAsync() { await client.GetAsync(url); await ReadDataAsync(); } // 编译器生成的状态机伪代码 class StateMachine : IAsyncStateMachine { int state = -1; // 执行位置标记 TaskAwaiter awaiter; void MoveNext() { switch (state) { case -1: awaiter = client.GetAsync(url).GetAwaiter(); if (!awaiter.IsCompleted) { state = 0; // 记录断点位置 awaiter.OnCompleted(MoveNext); // 注册回 return; // 返回控制权 } goto case 0; case 0: awaiter.GetResult(); // 获取结果 // 继续执行下一行代码... } } } ``` - **状态字段 `state`**:精确记录代码执行位置(类似程序计数器)[^1] - **回注册**:通过 `awaiter.OnCompleted()` 实现异步操作完成后的续体(continuation)执行[^2] #### 2. **await 关键操作流程** ▶️ ```mermaid sequenceDiagram participant Caller as 用线程 participant StateMachine as 状态机 participant AsyncOp as 异步操作(如I/O) Caller->>StateMachine: async方法 StateMachine->>AsyncOp: 启动异步操作 AsyncOp->>StateMachine: 返回未完成Task StateMachine->>Caller: 返回控制权(线程释放) Note over Caller: 线程可执行其他任务 AsyncOp-->>StateMachine: 操作完成信号 StateMachine->>Caller: 请求线程上下文 Caller->>StateMachine: 恢复执行(续体) ``` 1. **挂起检查**:遇到 `await` 时检查异步操作状态 - 若已完成:**同步继续执行**(不释放线程) - 若未完成:**挂起当前方法**,返回未完成的 `Task` 2. **线程释放**:控制权返回用方,**释放当前线程**(UI线程/线程池) 3. **续体注册**:向异步操作注册回方法(编译器生成) 4. **恢复执行**:异步操作完成后,**度器触发续体**在原上下文恢复执行[^3] #### 3. **核心设计特性** ⚙️ | 特性 | 原理说明 | |---------------------|--------------------------------------------------------------------------| | **非阻塞性** | 通过状态机保存执行上下文,释放当前线程 | | **上下文流动** | 默认捕获 `SynchronizationContext` 确保续体在原始上下文执行(如UI线程) | | **异常传播** | 异步操作异常通过 `Task.Exception` 传递到 `await` 点抛出 | | **值任务优化** | `ValueTask` 避免堆分配,适合高频用的异步操作 | #### 4. **典型执行场景对比** 🔍 ```csharp // 同步阻塞版本 ❌ void DownloadSync() { var data = client.Download(url); // 阻塞线程 Process(data); } // 异步非阻塞版本 ✅ async Task DownloadAsync() { var data = await client.DownloadAsync(url); // 挂起点 Process(data); // 恢复点 } ``` - **同步阻塞**:线程在 `Download()` 期间完全被占用(资源浪费) - **异步非阻塞**:数据下载期间线程可处理其他任务(如UI响应) #### 5. **性能优化实践** 🚀 ```csharp // 避免上下文捕获(后台任务) async Task ProcessDataAsync() { await ReadFileAsync().ConfigureAwait(false); // 放弃上下文捕获 // 此处在线程池执行 HeavyComputation(); // 需回UI线程时显式切换 Dispatcher.Invoke(() => UpdateUI()); } ``` - `ConfigureAwait(false)`:阻止上下文流动,**减少线程切换开销**[^1] - `ValueTask`:替代 `Task` 减少堆分配,适合结果常同步返回的操作 > 关键结论:async/await **不是多线程技术**,而是通过状态机实现的**非阻塞执行控制流**。其核心价值在于用同步写法实现异步操作,同时保证线程高效利用[^2][^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值