首页 > 基础资料 博客日记

上位机WPF全局异常捕获处理:三种场景全覆盖,防止崩溃

2026-06-14 11:30:01基础资料围观1

极客资料网推荐上位机WPF全局异常捕获处理:三种场景全覆盖,防止崩溃这篇文章给大家,欢迎收藏极客资料网享受知识的乐趣

上位机程序大多对接硬件、处理实时数据,一旦崩溃可能导致数据丢失、设备异常,甚至影响生产流程。今天就给大家整理WPF全局异常捕获完整方案,覆盖UI线程、异步任务、非UI非后台线程3大核心场景。

先明确核心目标:全局异常捕获不是“掩盖错误”,而是统一捕获、规范记录、友好提示、优雅恢复,既提升用户体验,又能快速定位问题根源。

一、捕获系统UI 线程上未处理的异常

WPF的UI线程是程序的“主线程”,所有界面交互(按钮点击、数据渲染、控件操作)都在这个线程上执行。一旦这里出现未处理异常,程序会直接闪退,这也是最容易遇到的崩溃场景。

WPF中,我们可以在App.xaml对应的App.xaml.cs中,通过注册 DispatcherUnhandledException 事件,捕获所有UI线程未处理异常。App.xaml作为WPF程序的全局入口,是处理全局异常的最佳切入点之一,其对应的代码隐藏类可直接关联全局事件处理逻辑。

public partial class App : Application
{
public App()
{
// 1. 捕获 UI 线程上未处理的异常(按钮点击、界面操作)
this.DispatcherUnhandledException += App_DispatcherUnhandledException;
}

///


/// UI 线程异常(最常用)
///

private void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
try
{
// 标记异常已处理,程序不崩溃
e.Handled = true;
// 进行日志记录
MessageBox.Show($"UI 异常:{e.Exception.Message}\n调用栈:{e.Exception.StackTrace}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
catch
{
// 忽略日志写入失败等二次异常
}
}
}

DispatcherUnhandledException:

1、捕获范围:UI线程上的所有未处理异常;

2、场景:按钮点击、事件绑定、界面加载、UI操作报错;

3、特点:标记 e.Handled = true 后,程序不会崩溃,可继续运行。

二、捕获 Task/异步任务 未观察到异常

上位机开发中,为了避免UI卡顿,我们经常用Task开启异步任务,比如:异步读取硬件数据、异步处理大批量数据、异步调用接口等。

这里有个隐藏陷阱:如果Task中的异常没有被观察(没有try-catch、没有Wait()、没有获取Result),程序不会立即崩溃,但当Task被GC(垃圾回收)回收时,会触发程序崩溃,而且这种崩溃很难排查——因为崩溃时机不确定,也没有明显的报错提示。比如:用Task.Run()开启一个异步任务,里面出现空引用异常,但没有任何捕获逻辑,程序可能在运行一段时间后突然闪退。

通过注册TaskScheduler.UnobservedTaskException事件,可捕获所有未观察到的Task异常。该事件能监控所有Task中未被显式处理的异常,即使没有调用Wait()或获取Result,也能精准捕获。

public partial class App : PrismApplication
{
public App()
{
// 2. 捕获 Task/异步任务 未观察到的异常
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}

///


/// Task 异步任务未捕获异常(await 没写 try-catch)
///

private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
try
{
// 标记异常已观察,防止程序崩溃
e.SetObserved();

// 进行日志记录
MessageBox.Show($"异步任务异常:{e.Exception.Message}", "异步错误", MessageBoxButton.OK, MessageBoxImage.Warning);
}
catch
{
}
}
}

TaskScheduler.UnobservedTaskExcwption:

1、捕获范围:异步Task未被观察的异常;

2、场景:async/await 没写 try-catch、忘记等待的Task抛错;

3、特点:调用 e.SetObserved() 后,程序不会崩溃。

三、捕获 非UI线程、非后台线程未处理异常

除了UI线程和Task异步线程,上位机开发中还可能用到“自定义线程”(比如:用Thread类开启的线程,处理持续的硬件数据监听)。

这种非UI、非后台的自定义线程,其未处理异常不会被上面两种方式捕获,一旦出现异常,程序会直接崩溃,属于“兜底场景”,必须单独处理。

通过注册 AppDomain.CurrentDomain.UnhandledException事件,捕获整个应用程序域内所有未处理的异常,作为最后的“兜底”捕获,覆盖所有未被前面两种方式捕获的异常场景,包括自定义非UI线程异常。

public partial class App : PrismApplication
{
public App()
{
// 3. 捕获非 UI 线程、后台线程未处理异常,最后异常捕获的兜底行为
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}

///


/// 非 UI 线程/后台线程异常
///

private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
try
{
// 异常对象转换
var ex = e.ExceptionObject as Exception;

// 日志记录
MessageBox.Show($"全局线程异常:{ex?.Message}", "致命错误", MessageBoxButton.OK, MessageBoxImage.Stop);
}
catch
{
}
}
}

AppDomain.UnhandledException:

1、捕获范围:非UI线程、后台线程、所有未捕获的异常;

2、场景:手动 new Thread()、线程池、无try-catch的后台代码;

3、特点: 无法阻止程序退出,只能用来记录日志、提示用户、保存数据。

四、总结

1、UI异常:用 DispatcherUnhandledException,90%异常都能捕获并让程序继续运行;

2、异步异常:用 TaskScheduler.UnobservedTaskException,防止异步任务异常未处理;

3、全局兜底:AppDomain.CurrentDomain.UnhandledException 做最后防线,只用于日志、保存数据,无法阻止崩溃;

4、不要依赖全局异常捕获:核心业务逻辑必须写try-catch,全局只做兜底和日志记录;

5、用户提示要区分场景:UI异常可详细提示,后台异常尽量简洁,避免干扰现场操作;

6、异步任务优先用async/await+内部try-catch,减少未观察异常的出现。

欢迎关注微信公众号,第一时间获取更新:

博客园图片

文章来源:https://www.cnblogs.com/jianghuxing/p/20517678
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:jacktools123@163.com进行投诉反馈,一经查实,立即删除!

标签:

相关文章

本站推荐

标签云