全局异常处理需分场景布防:winforms用threadexception捕获ui同步异常,但不处理await异步回调;appdomain.unhandledexception已弃用,.net core+应使用useexceptionhandler()或taskscheduler.unobservedtaskexception;async void是黑洞,必须改为async task并await。

全局异常处理不是“加个 try-catch 就完事”,而是分线程、分上下文、分生命周期的系统性补漏。C# 里没有单一入口能捕获所有异常,必须按场景分别布防。
Application.ThreadException 只管 WinForms UI 线程同步异常
它只捕获按钮点击、控件事件、Paint 重绘等 UI 主线程中同步抛出的异常。一旦你在事件里用了 await,后续异步回调里的异常就完全绕过它。
- 注册必须在
Application.Run()之前,且仅对STAThread主线程有效 - 它不捕获
TaskScheduler.UnobservedTaskException或后台线程异常 - 如果没做
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException),部分 .NET 版本下仍可能弹原生错误框
AppDomain.UnhandledException 已被弃用,别再用
.NET Core / .NET 5+ 中 AppDomain 已移除,该事件在现代项目中根本不会触发。强行保留会导致编译警告,运行时静默失效。
替代方案是:HostBuilder 的 UseExceptionHandler()(Web API),或 TaskScheduler.UnobservedTaskException(针对未 await 的 Task 异常)。
-
AppDomain.CurrentDomain.UnhandledException在 .NET 6+ 控制台项目中注册后也不会触发,除非你显式降级到 .NET Framework 兼容模式 - 它无法阻止进程退出,只能做最后日志记录
ASP.NET Core 中 UseExceptionHandler() 不捕获 400 类错误
UseExceptionHandler() 默认只捕获未处理的 5xx 异常(如 NullReferenceException),而 ModelState.IsValid == false 导致的 400 响应、参数绑定失败、路由找不到等,压根不会进这个中间件。
- 要统一处理模型验证失败,得配合
InvalidModelStateResponseFactory配置 - 自定义
ProblemDetails响应需手动设置Status和Type,否则默认返回 500 - 若在 Controller 里手动 throw
ArgumentException,需注册对应ExceptionFilter才能映射为 400
async void 是全局异常处理的黑洞
任何 async void 方法(比如 WinForms 事件处理器写成 async void Button_Click)内部抛出的异常,既不会被 ThreadException 捕获,也不会进入 TaskScheduler.UnobservedTaskException,而是直接炸掉线程、终止应用。
- 必须改写为
async Task,并在调用处await - WinForms 中无法 await 事件处理器?那就用
try/catch包住整个async方法体,再把异常转发给Application.ThreadException处理逻辑 - 第三方控件触发的
async void事件?只能靠TaskScheduler.UnobservedTaskException+ 日志兜底,但此时堆栈已丢失原始上下文
最易被忽略的一点:全局异常处理器本身不能抛异常。一旦 UseExceptionHandler 里日志组件崩了,或 ThreadException 回调里 MessageBox.Show 被禁用(如服务环境下),整个兜底链就断了——程序会直接崩溃,连日志都来不及写。










