当前位置:首页 » 《随便一记》 » 正文

浅析 C# Async 和 Await 【代码之美系列】

19 人参与  2024年12月21日 14:01  分类 : 《随便一记》  评论

点击全文阅读



文章目录

一、引言 ?二、异步编程的基本概念三、Async 和 Await 的基本用法四、Async 方法的转换过程五、Await 的工作原理六、异步方法的执行流程七、异常处理八、避免常见陷阱8.1 死锁8.2 忘记await8.3 过度使用 async void 九、高级模式9.1 并行执行多个任务9.2 带超时的异步操作 十、结论十一、代码之美系列目录???


一、引言 ?

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

二、异步编程的基本概念

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

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

三、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 完成,触发 continuationMethod1Async 继续执行,打印"3. 方法1结束"。Method1Async 完成,触发 MainMethodAsynccontinuationMainMethodAsync 继续执行,打印"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.WhenAnyTask.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;    }}

十、结论

asyncawait 极大地简化了 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最全总结

在这里插入图片描述


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/204657.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1