在应用程序设计里面,不单是 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 线程顶层异常的更多相关文章

  1. 《C#并发编程经典实例》学习笔记—2.9 处理 async void 方法的异常

    问题 需要处理从 async void 方法传递出来的异常. 解决方案 书中建议尽量不写 async void 这样的方法,如果非写不可,建议在方法内部 try catch 所有的代码,即在方法内部处 ...

  2. 为什么不要使用 async void?

    问题 在使用 Abp 框架的后台作业时,当后台作业抛出异常,会导致整个程序崩溃.在 Abp 框架的底层执行后台作业的时候,有 try/catch 语句块用来捕获后台任务执行时的异常,但是在这里没有生效 ...

  3. springboot+@async异步线程池的配置及应用

    示例: 1. 配置 @EnableAsync @Configuration public class TaskExecutorConfiguration { @Autowired private Ta ...

  4. 为什么不要使用 Async Void ?

    原文:为什么不要使用 Async Void ? 问题 在使用 Abp 框架的后台作业时,当后台作业抛出异常,会导致整个程序崩溃.在 Abp 框架的底层执行后台作业的时候,有 try/catch 语句块 ...

  5. WPF 线程中异常导致程序崩溃

    一般我们WPF中都加全局捕获,避免出现异常导致崩溃. Application.Current.DispatcherUnhandledException += Current_DispatcherUnh ...

  6. 用C++11的std::async代替线程的创建

    c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + 1); t.join(); 但是线程毕竟是属于比 ...

  7. (原创)用C++11的std::async代替线程的创建

    c++11中增加了线程,使得我们可以非常方便的创建线程,它的基本用法是这样的: void f(int n); std::thread t(f, n + ); t.join(); 但是线程毕竟是属于比较 ...

  8. Java的Hook线程及捕获线程执行异常

    import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.f ...

  9. spring boot:使用async异步线程池发送注册邮件(spring boot 2.3.1)

    一,为什么要使用async异步线程池? 1,在生产环境中,有一些需要延时处理的业务场景: 例如:发送电子邮件, 给手机发短信验证码 大数据量的查询统计 远程抓取数据等 这些场景占用时间较长,而用户又没 ...

  10. 《C#并发编程经典实例》学习笔记—2.8 处理 async Task 方法的异常

    异常处理一直是所有编程语言不可避免需要考虑的问题,C#的异步方法的异常处理和同步方法并无差别,同样要借助 try catch 语句捕获异常. 首先编写一个抛出异常的方法 static async Ta ...

随机推荐

  1. python高级技术(网络编程一)

    一  socket是什么 链接socket前要熟悉计算机网络基础请看链接:https://www.cnblogs.com/coderxueshan/p/17344739.html Socket是应用层 ...

  2. 「AntV」全球AQI数据获取与L7可视化

    1. 引言 L7 地理空间数据可视分析引擎是一种基于 WebGL 技术的地理空间数据可视化引擎,可以用于实现各种地理空间数据可视化应用.L7 引擎支持多种数据源和数据格式,包括 GeoJSON.CSV ...

  3. openlayers操作分享:如何从容的在vue中食用openlayers6

    这篇文章,分享下我对openlayers的一些经验和理解,会夹杂大量搜索出来得文档,是我正式使用时可以实现的,废话不多说,我们从下载开始 一,openlayers安装且初始化地图 创建vue项目就省略 ...

  4. 记录--Vue3+TS(uniapp)手撸一个聊天页面

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 Vue3+TS(uniapp)手撸一个聊天页面 前言 最近在自己的小程序中做了一个智能客服,API使用的是云厂商的API,然后聊天页面.. ...

  5. 温馨提示:不注意这几点,PDT(产品开发团队)就得散!

    在IPD(集成产品开发)体系中,PDT(Product Development Team,产品开发团队)发挥着至关重要的作用.PDT是一个跨部门.跨职能的协作团队,其成员来自不同的专业领域,包括研发. ...

  6. #矩阵树定理,高斯消元#洛谷 4111 [HEOI2015]小 Z 的房间

    题目 分析 题目要求生成树个数,求出基尔霍夫矩阵后高斯消元, 但是这里模数不是质数,所以要辗转相除法 代码 #include <cstdio> #include <cctype> ...

  7. Jetty的server模块

    启用server模块,执行如下命令: java -jar $JETTY_HOME/start.jar --add-modules=server 命令的输出,如下: INFO : server init ...

  8. IDEA社区版,真香!

    IDEA(IntelliJ IDEA)是众多 Java 开发者的首选. 商业版的昂贵 IDEA 商业版(IntelliJ IDEA Ultimate)功能非常强大,能够满足 Java 开发的所有需求, ...

  9. 我只用了3步,实现了一个逼真的3D场景渲染

    给3D模型及环境场景渲染出兼具质感和真实感的材质效果,需要经历几步? 显然,目前的3D模型材质渲染技术,还无法实现简单几步就能搞定的标准化作业来量化,完成一个质量过关的3D模型渲染,一般需要: 1.准 ...

  10. JS启动Windows上的exe

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...