一、async/await 异步编程实现机制

1.1 核心概念

async/await 是 C# 5.0 引入的语法糖,它基于**状态机(State Machine)**模式实现,将异步方法转换为编译器生成的状态机类。

1.2 编译器转换过程

当编译器遇到 async 方法时,会将其转换为一个实现了 IAsyncStateMachine 接口的状态机类。

// 原始代码
public async Task<int> GetDataAsync()
{await Task.Delay(1000);return 42;
}

编译器会将其转换为类似以下结构:

// 伪代码:编译器生成的状态机
[CompilerGenerated]
private sealed class <GetDataAsync>d__1 : IAsyncStateMachine
{public int <>1__state;public AsyncTaskMethodBuilder<int> <>t__builder;public YourClass <>4__this;private TaskAwaiter <>u__1;public void MoveNext(){int num = <>1__state;try{TaskAwaiter awaiter;if (num != 0){awaiter = Task.Delay(1000).GetAwaiter();if (!awaiter.IsCompleted){<>1__state = 0;<>u__1 = awaiter;<>t__builder.AwaitOnCompleted(ref awaiter, ref this);return;}}else{awaiter = <>u__1;<>u__1 = default(TaskAwaiter);<>1__state = -1;}awaiter.GetResult(); // 清理异常<>t__builder.SetResult(42); // 设置返回值}catch (Exception e){<>1__state = -2;<>t__builder.SetException(e);return;}}public void SetStateMachine(IAsyncStateMachine stateMachine){<>t__builder.SetStateMachine(stateMachine);}
}

1.3 关键组件解析

1.3.1 AsyncTaskMethodBuilder
  • 负责管理异步方法的生命周期
  • 包含 Task 的创建、状态管理和结果设置
  • 提供 AwaitOnCompletedSetResultSetException 等方法
1.3.2 状态机工作流程
  1. 初始状态 (<>1__state = -1):方法开始执行
  2. 等待状态 (<>1__state = 0):遇到 await 且任务未完成
  3. 完成状态 (<>1__state = -2):方法执行完毕或发生异常
1.3.3 await 操作的执行过程
  1. 调用 GetAwaiter() 获取 TaskAwaiter
  2. 检查 IsCompleted 属性
  3. 如果未完成:
    • 保存当前状态
    • 注册 continuation 回调
    • 返回控制权给调用者
  4. 如果已完成:继续执行后续代码

1.4 上下文捕获(Context Capture)

await 默认会捕获当前的 SynchronizationContextTaskScheduler

public async Task ProcessAsync()
{// 捕获当前上下文await SomeAsyncOperation();// 回到原始上下文执行后续代码UpdateUI(); // 在UI线程上执行
}

二、死锁产生的原因

2.1 同步阻塞导致的死锁

最常见的死锁场景:在同步代码中阻塞等待异步操作完成。

// 危险代码 - 可能导致死锁
public int GetData()
{// 死锁!等待异步方法完成return GetDataAsync().Result;
}public async Task<int> GetDataAsync()
{await Task.Delay(1000);return 42;
}

死锁形成过程:

  1. GetData() 调用 GetDataAsync()
  2. GetDataAsync() 开始执行,遇到 await
  3. 线程池线程被释放,GetData() 在主线程阻塞等待
  4. await 完成后,需要回到原始上下文(主线程)继续执行
  5. 但主线程被 Result 阻塞,无法执行 continuation
  6. 形成死锁

2.2 UI线程死锁

在WinForms/WPF应用中特别常见:

private async void Button_Click(object sender, EventArgs e)
{// 危险:在UI事件中同步等待var result = GetDataAsync().Result;textBox.Text = result.ToString();
}

2.3 ASP.NET 经典死锁

在ASP.NET Framework中:

public ActionResult GetData()
{// 可能死锁var data = GetDataAsync().Result;return Json(data);
}

三、死锁解决方案

3.1 根本原则:避免同步阻塞

错误做法:

// ❌ 避免使用
var result = DoAsync().Result;
var result = DoAsync().Wait();
var result = DoAsync().GetAwaiter().GetResult();

正确做法:

// ✅ 使用 async/await 链式调用
public async Task<int> GetDataAsync()
{return await GetDataAsync();
}

3.2 解决方案一:异步编程链

将同步方法改为异步:

// 原始同步方法
public int GetData()
{return GetDataAsync().Result; // 死锁风险
}// 改为异步方法
public async Task<int> GetDataAsync()
{return await GetDataAsync();
}// 调用者也需要异步
public async Task ProcessAsync()
{var data = await GetDataAsync();// 处理数据
}

3.3 解决方案二:ConfigureAwait(false)

在类库中使用 ConfigureAwait(false) 避免上下文捕获:

public async Task<int> GetDataAsync()
{// 不捕获上下文,避免死锁await Task.Delay(1000).ConfigureAwait(false);// 继续异步操作await AnotherAsyncOperation().ConfigureAwait(false);return 42;
}

使用场景:

  • 类库开发
  • 不需要访问UI组件的后台操作
  • ASP.NET Core 应用

3.4 解决方案三:创建新线程执行

当必须同步调用时,使用新线程:

public int GetData()
{// 在新线程中执行异步方法return Task.Run(async () => await GetDataAsync()).Result;
}

四、最佳实践

4.1 类库开发

// 类库中始终使用 ConfigureAwait(false)
public async Task<ServiceResult> CallServiceAsync()
{var response = await httpClient.GetAsync(url).ConfigureAwait(false);var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false);return JsonConvert.DeserializeObject<ServiceResult>(content);
}

4.2 UI应用开发

// UI事件处理保持异步
private async void Button_Click(object sender, EventArgs e)
{try{button.Enabled = false;var result = await GetDataAsync(); // 不使用 .ResulttextBox.Text = result.ToString();}catch (Exception ex){MessageBox.Show(ex.Message);}finally{button.Enabled = true;}
}

4.3 异步Main方法

// .NET 4.7.1+ 支持 async Main
static async Task<int> Main(string[] args)
{try{await ProcessAsync();return 0;}catch (Exception ex){Console.WriteLine(ex.Message);return 1;}
}

五、总结

  1. async/await 是基于状态机的编译器魔法
  2. 死锁 主要由同步阻塞和上下文捕获引起
  3. 最佳解决方案 是保持异步调用链
  4. 类库开发 应使用 ConfigureAwait(false)
  5. 避免 在异步代码中使用 .Result.Wait()

遵循这些原则,可以安全高效地使用C#的异步编程模型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/917538.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/917538.shtml
英文地址,请注明出处:http://en.pswp.cn/news/917538.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Servlet 学习笔记

本文为记录Servlet学习时的一些笔记和代码 课程参考黑马程序员 对于Java Web 学习的一个复习一 概述server applet 运行在服务器端的小程序 本质就是一个接口 定义java类被浏览器访问到&#xff08;Tomcat识别&#xff09;的规则我们会自定义这样一个类来实现复写方法实现接口二…

【maven】仓库配置

目录 一、本地仓库 二、私有仓库 三、阿里云仓库 一、本地仓库 针对无外网、无maven私服&#xff0c;只有本地仓库&#xff0c;进行maven项目开发。在maven的settings.xml中设置三项&#xff1a; 1、本地仓库地址 默认在当前系统用户下创建目录&#xff1a;.m2/repository…

信息系统架构设计的系统性解析

一、信息系统架构设计​​概念定义​​&#xff1a;信息系统架构&#xff08;ISA&#xff09;是对系统组件、交互关系及环境约束的结构化抽象&#xff0c;确保业务目标与技术实现对齐。核心要素包括业务逻辑层、数据层、应用层和基础设施层。​​设计方法​​&#xff1a;​​T…

编译旧版本的electron内核

我们的软件产品使用的electron框架&#xff0c;electron框架是不断更新的&#xff0c;但是我们的软件太过庞大复杂&#xff0c;不敢轻易升级electron框架。Electron旧版本存在一些BUG和不合理的地方&#xff0c;需要去修复BUG或者不合理的地方。修复完BUG后&#xff0c;要对ele…

Elastic 9.1/8.19:默认启用 BBQ,ES|QL 支持跨集群搜索(CCS)正式版,JOINS 正式版,集成 Azure AI Foundry

作者&#xff1a;来自 Elastic Mark Doncov 今天&#xff0c;我们很高兴宣布 Elastic 9.1 和 8.19 正式发布&#xff01; 是的&#xff0c;又有好消息 —— 我们将 8.x 系列最终延长到 8.19&#xff0c;这样那些还在等待升级到 9.x 的用户也能享受到许多新功能。 Elastic 9.1…

Redis面试精讲 Day 8:Stream消息队列设计与实现

【Redis面试精讲 Day 8】Stream消息队列设计与实现 文章标签 Redis,消息队列,Stream,面试技巧,分布式系统,后端开发 文章简述 本文是"Redis面试精讲"系列第8天&#xff0c;聚焦Redis 5.0引入的Stream消息队列。文章深入解析Stream的核心概念与实现原理&#xff0…

【01】大恒相机SDK C++开发 —— 初始化相机,采集第一帧图像、回调采集、关闭相机

文章目录1 初始化相机&#xff0c;采集第一帧图像2 回调方式采集图像3 视频教程1 初始化相机&#xff0c;采集第一帧图像 #include <iostream> #include <GalaxyIncludes.h> using namespace std;int main() {//首先&#xff0c;对相机资源进行初始化IGXFactory::…

Windows下定位Mingw编译的Qt程序崩溃堆栈

一、dump和pdb是什么 在Windows系统下&#xff0c;当我们写的程序跑在客户的机器上&#xff0c;因为一个bug&#xff0c;导致程序崩溃&#xff0c;我们该如何定位并修复这个bug呢&#xff1f; 有人会说记录日志&#xff0c;即便有日志&#xff0c;也是不好定位的&#xff0c;因…

.net依赖注入框架 Autofac和MEF的对比

Autofac 默认需要显式注册每个类型&#xff0c;这是它与MEF在模块化设计上的主要区别。以下是具体对比说明&#xff1a;1. Autofac 的基本注册方式 Autofac 必须通过代码明确注册每个需要注入的类型&#xff08;除非使用特殊扫描机制&#xff09;&#xff1a; var builder new…

Python 使用 asyncio 包处理并 发(使用asyncio包编写服务器)

使用asyncio包编写服务器 演示 TCP 服务器时通常使用回显服务器。我们要构建更好玩一点的示 例服务器&#xff0c;用于查找 Unicode 字符&#xff0c;分别使用简单的 TCP 协议和 HTTP 协议实现。这两个服务器的作用是&#xff0c;让客户端使用 4.8 节讨论过的 unicodedata 模块…

Node.js (Express) + MySQL + Redis构建项目流程

以下是使用 Node.js (Express) MySQL Redis 构建完整项目的详细流程&#xff0c;涵盖环境搭建、架构设计、核心代码实现和部署优化&#xff1a;一、项目初始化 1. 创建项目目录 mkdir my-project cd my-project npm init -y2. 安装基础依赖 npm install express mysql2 redis…

Python3 中使用zipfile进行文件(夹)的压缩、解压缩

一、文件压缩与解压缩模块 zipfile简介 zipfile 是 Python 标准库中用于处理 ZIP 压缩文件的模块&#xff0c;提供了创建、读取、写入、解压 ZIP 文件的完整功能。它支持多种压缩算法&#xff0c;无需安装额外依赖&#xff0c;是处理 ZIP 格式的首选工具。 核心功能与常用类 zi…

在Java客户端使用Redis

目录 第一步&#xff1a;开放Redis外部连接配置 第二步&#xff1a;配置端口转发 第三步&#xff1a;在IDEA中导入依赖 第四步&#xff1a;编写代码命令 连接环境&#xff1a;Java客户端为本地IDEA&#xff0c;Redis服务器安装在云服务器Ubuntu系统中。 第一步&#xff1a;开…

【MySQL】MySQL索引—B树/B+树

目录 1. 数据库索引 1.1 索引的概念 1.2 索引的特点 1.3 索引查询对比普通的查询 1.4 索引的操作 1.5 索引的原理 1.6 B树 1.7 B树 1.8 B树的优点 1. 数据库索引 1.1 索引的概念 数据库的索引是一种特殊的数据结构&#xff0c;里面包含着数据表中所有记录的引用&…

jQuery Mobile 面板详解

jQuery Mobile 面板详解 引言 随着移动设备的普及,移动网页开发变得越来越重要。jQuery Mobile 是一个基于 jQuery 的移动网页开发框架,它提供了一套丰富的 UI 组件和主题,使得开发者可以快速构建出美观、响应式的移动网页。在 jQuery Mobile 中,面板(Panel)是一个非常…

Python中的import和from...import有什么区别?

文章目录 前言 一、import导入模块 导入模块并给它一个别名 语法格式 二、from...import导入特定项 1.导入模块中的特定项 2.导入模块中的所有项 2.1 命名空间核污染 2.2 性能影响 总结 前言 在Python编程中,模块和包的导入机制是编写可维护、可扩展代码的核心。深入理解Pyth…

vscode提示“无法使用 compilerPath 解析配置”解决办法

0 问题描述 使用vscode的Remote-SSH插件连接安装在虚拟机上的Windows10进行远程开发时&#xff0c;出现如下提示&#xff1a;无法使用 compilerPath 解析配置:“D:\mingw64\bin\gcc.exe” 所有包含C库头文件的文件都被标红提示错误&#xff1a;1 问题原因 vscode没有设置正确的…

信噪比(Signal-to-Noise Ratio, SNR)详细介绍

信噪比&#xff08;Signal-to-Noise Ratio, SNR&#xff09;信噪比&#xff08;Signal-to-Noise Ratio&#xff0c;SNR&#xff09;是衡量信号质量的重要参数&#xff0c;表示有用信号的功率与背景噪声功率的比值。SNR在通信、音频处理、视频处理以及其他电子信号处理领域中具有…

Nginx 相关实验(1)

nginx源码编译 本实验采用nginx源码编译的安装方式&#xff0c;需要准备一个tar包&#xff0c;可从nginx官网上下载。 下载地址&#xff1a;nginx: downloadhttps://nginx.org/en/download.html 将下载好的压缩包传到虚拟机中的自定义目录下 [rootwebserver ~]# ls anacond…

【选型】HK32L088 与 STM32F0/L0 系列 MCU 参数对比与选型建议(ST 原厂 vs 国产芯片)(单片机选型主要考虑的参数与因素)

国产 vs ST 单片机在工业控制中的性能对比分析 HK32L088 与 STM32F0/L0 系列 MCU 参数对比与选型建议 工业控制领域 MCU 选型:国产航顺 HK32 与 ST 原厂芯片深入比较 国产 MCU 是否可替代 ST?基于发电机控制应用的深入评估 从数据手册看 MCU 制造工艺差异:HK32L088 vs S…