dotnet 警惕 async void 线程顶层异常
在应用程序设计里面,不单是 dotnet 应用程序,绝大部分都会遵循让应用在出现未处理异常状态时终结的原则。在 dotnet 应用里面,如果一个线程顶层出现未捕获异常,则应用进程将会被认为出现异常状态而退出。通常来说就是未捕获异常导致进程闪退
在 dotnet 里面,有一个隐藏的陷阱,那就是 async void 将会在没有线程同步上下文的情况下,被当成线程顶层。如果在 async void 里面发生任何未捕获的异常,严重的话将会导致进程闪退
如以下代码,在当前执行线程没有线程同步上下文的情况下,抛出的异常将会让进程闪退
async void Foo()
{
...
throw new Exception("林德熙是逗比");
}
为什么这里和线程同步上下文相关?原因是在有线程同步上下文时,执行都委托调度器执行,比如经典的线程同步上下文 WPF 主 UI 线程。这个时候主 UI 线程在 async void 里面抛出的异常是到达 Dispatcher 里,而不是线程顶层。于是可以通过全局的方式捕获异常
在 dotnet 里面,在当前 2023 没有机制可以统一捕获 async void 的异常,防止进程闪退。我在 dotnet 运行时官方仓库和大佬们讨论过这个问题,大佬的认为是当前 dotnet 的行为是符合预期和符合文档的,但我持有不同的想法,我认为这样的行为是不能做出可靠稳定的应用的,详细请看 https://github.com/dotnet/runtime/issues/76367
在 dotnet 里的另一个更加隐藏的陷阱是事件的加等里面出现异步,如以下代码
FooEvent += async (s, e) =>
{
...
throw new Exception("林德熙是逗比");
}
以上的代码里面隐式定义了 async void 方法,如此也会在当线程不在同步上下文时,抛出异常炸掉进程
解决方法是在这些 async void 方法里面自行根据业务需求,捕获异常。在大部分应用里面,一般都是应该在此捕获所有异常,除非可以无视应用进程闪退问题
以下是另外更多的行为细节
在 dotnet 里面的 async void 抛出的未捕获异常,将会进入到 AppDomain 的 UnhandledException 事件里面,然而此事件里面的 IsTerminating 属性将都是 true 且不可接住。如果进入了 UnhandledException 事件里面,还不想让进程退出,我所知道的方法只有是通过 Thread.Sleep 让当前线程不再执行,但显然这是一个很诡异的方式
在 dotnet 里面的 Task 的行为却和 async void 差异比较大,比较符合咱的认知。将 async void 改为 async Task 然后抛出未捕获异常,此时如果方法返回的 Task 没有被任何等待,将会在 Task 对象被 GC 时进入 TaskScheduler.UnobservedTaskException 事件,此事件不会导致任何的进程退出。准确来说是在 .NET Framework 4.5 开始,就不会因为 TaskScheduler.UnobservedTaskException 里的异常导致进程退出
这是因为在 Task 里面,一开始的设计也是和 async void 一样导致进程退出,然而在实际应用里面,大家都发现 Task 被等待这个事情不由实现方决定,如此导致了大量的进程退出的不可用问题,于是后面大佬就决定让 Task 里面的异常只是进入 TaskScheduler.UnobservedTaskException 事件,不会作为线程顶层异常让进程退出。另外在 .NET Framework 4.5 之后,对 Task 与线程之间的关系做了一些底层优化,导致 Task 里面执行的逻辑从逻辑上说不再属于线程顶层,这部分细节过于复杂,我自己的了解也不够就不在博客里讲了
通过本文可以了解到,在 dotnet 里面隐藏了 async void 和异步无返回值事件或委托加等逻辑里面可能出现的因为未捕获异常导致的进程闪退问题。其中的解决方法就是要么在这些代码逻辑里面捕获所有异常规避问题,要么尝试将 async void 改造为 async Task 规避问题
这里还必须着重说明的是,捕获线程顶层异常时,最好采用捕获所有异常的方式,因为可能自己的代码本来认为不会存在任何异常的逻辑,但实际运行可能遇到 OutOfMemoryException 等通用运行异常
另外在捕获异常用来记录日志的逻辑,也推荐使用双层捕获方式,解决记录异常的模块抛出的异常炸掉应用
我依然认为 async void 线程顶层异常无法统一处理导致进程退出是 dotnet 的基础设计缺陷
dotnet 警惕 async void 线程顶层异常的更多相关文章
- 《C#并发编程经典实例》学习笔记—2.9 处理 async void 方法的异常
问题 需要处理从 async void 方法传递出来的异常. 解决方案 书中建议尽量不写 async void 这样的方法,如果非写不可,建议在方法内部 try catch 所有的代码,即在方法内部处 ...
- 为什么不要使用 async void?
问题 在使用 Abp 框架的后台作业时,当后台作业抛出异常,会导致整个程序崩溃.在 Abp 框架的底层执行后台作业的时候,有 try/catch 语句块用来捕获后台任务执行时的异常,但是在这里没有生效 ...
- springboot+@async异步线程池的配置及应用
示例: 1. 配置 @EnableAsync @Configuration public class TaskExecutorConfiguration { @Autowired private Ta ...
- 为什么不要使用 Async Void ?
原文:为什么不要使用 Async Void ? 问题 在使用 Abp 框架的后台作业时,当后台作业抛出异常,会导致整个程序崩溃.在 Abp 框架的底层执行后台作业的时候,有 try/catch 语句块 ...
- WPF 线程中异常导致程序崩溃
一般我们WPF中都加全局捕获,避免出现异常导致崩溃. Application.Current.DispatcherUnhandledException += Current_DispatcherUnh ...
- 用C++11的std::async代替线程的创建
c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比 ...
- (原创)用C++11的std::async代替线程的创建
c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + ); t.join(); 但是线程毕竟是属于比较 ...
- Java的Hook线程及捕获线程执行异常
import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.f ...
- spring boot:使用async异步线程池发送注册邮件(spring boot 2.3.1)
一,为什么要使用async异步线程池? 1,在生产环境中,有一些需要延时处理的业务场景: 例如:发送电子邮件, 给手机发短信验证码 大数据量的查询统计 远程抓取数据等 这些场景占用时间较长,而用户又没 ...
- 《C#并发编程经典实例》学习笔记—2.8 处理 async Task 方法的异常
异常处理一直是所有编程语言不可避免需要考虑的问题,C#的异步方法的异常处理和同步方法并无差别,同样要借助 try catch 语句捕获异常. 首先编写一个抛出异常的方法 static async Ta ...
随机推荐
- 记录--浏览器渲染15M文本导致崩溃怎么办
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 最近,我刚刚完成了一个阅读器的txt文件阅读功能,但在处理大文件时,遇到了文本内容过多导致浏览器崩溃的问题. 一般情况下,没有任何样式渲染 ...
- 快速上手系列:传智播客Java基础学习笔记
配置环境,把JDK的bin所在路径复制到Path,末尾加; 基本语法 一 常见的DOS命令 盘符的切换 d:回车 目录的进入 cd javase c ...
- C# 单例模式使用 Singleton
Singleton 类如下: public class Singleton<T> where T : class, new() { private static T _instance; ...
- 超越极限!80Gbps高速传输,让您的数据瞬间飞速传递
大文件传输是很多企业面临的挑战之一.基于传统的文件传输方法,由于许多原因,例如网络拥塞.数据包丢失.传播延迟等,导致文件的传输速度较慢.不稳定或不安全.尤其是对于像科研机构.金融公司和媒体制作公司等需 ...
- Minlexes题解
\(\texttt{Problem Link}\) 简要题意 在一个字符串 \(s\) 中,对于每个后缀,任意删掉一些相邻的相同的字符,使得字符串字典序最小. 注意:删掉之后拼起来再出现的相邻相同字符 ...
- ET介绍——更为便捷高效的AI框架-行为机(Behavior Machine)
什么是行为机 顾名思义,类比状态机每个节点是一个状态,行为机每个节点是描述一种行为.行为机每个节点之间是互斥的,并且节点相互之间完全不用关心是怎么切换的.这里就不讲状态机跟行为树是怎么做ai的了,这里 ...
- 三步就能在OpenHarmony中实现车牌识别
介绍 本车牌识别项目是基于开源项目 EasyPR(Easy to do Plate Recognition)实现.EasyPR 是一个开源的中文车牌识别系统,基于 OpenCV 开源库开发. 本项目使 ...
- wireshark 抓包整理———— 从一个小案例开始 [一]
前言 前面已经有抓包系列了,简单写一下wireshark的抓包系列,共36节,18个理论小栗子,36个实战栗子. 正文 这个例子是<<wireshark 分析就这么简单>>的一 ...
- 2024年GPLT团体程序设计比赛L2-D吉利矩阵题解
只能说比赛时前期做得太慢了,后面导致题目只能捞点分数(IOI赛制),当时这道题是我不剪枝DFS拿了4分,压线拿铜牌! 考完试一做,发现是个大水题(bushi) 主要原理:DFS(深度优先搜索) + 剪 ...
- MAC Book: Operation not permitted
背景: 最近清理系统上的一些无用的文件后,为了release出可用空间,所以还要把.Trash目录下的文件清理才真正清理完,但是ls 查看该目录时发现一直报"operation not pe ...