.NET MAUI(WinUI 3)中如何在UI线程显示全局未处理异常对话框?
.NET MAUI(WinUI 3)全局异常处理实现方案
针对从Xamarin.Forms UWP迁移到.NET MAUI WinUI3项目的全局异常处理需求,以下是适配后的实现方案,重点解决UI线程显示对话框的问题:
一、全局异常事件订阅
在App.xaml.cs的构造函数中,订阅三类全局异常事件,覆盖不同场景的未处理异常:
public App() { InitializeComponent(); // 订阅AppDomain级别的未处理异常(非UI线程可能抛出) AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; // 订阅未观察到的任务异常(异步任务未捕获的异常) TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; // 订阅WinUI 3 UI线程的未处理异常 #if WINDOWS Microsoft.UI.Xaml.Application.Current.UnhandledException += WinUI_UnhandledException; #endif MainPage = new MainPage(); }
二、异常处理逻辑实现
1. WinUI 3 UI线程异常处理(原Xamarin对应逻辑)
针对WinUI3平台的UI线程异常,使用WinUI原生ContentDialog替代已弃用的MessageDialog,并确保在UI线程执行,同时指定XamlRoot解决上下文问题:
#if WINDOWS private async void WinUI_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e) { try { var exception = e.Exception; // 构造异常日志 var crashLog = $"Unhandled Exception | Message: {exception.Message} " + $"| Target Method: {exception.TargetSite?.Name} " + $"| Target Class: {exception.TargetSite?.DeclaringType?.FullName}"; // 记录异常日志(保持原项目的Log工具即可) Log.Fatal(crashLog, exception); // 标记异常已处理,避免应用崩溃 e.Handled = true; var currentWindow = Microsoft.UI.Xaml.Window.Current; if (currentWindow?.Content == null) return; // 在UI线程显示对话框 await currentWindow.Dispatcher.RunAsync(Microsoft.UI.Dispatching.CoreDispatcherPriority.Normal, async () => { var errorDialog = new Microsoft.UI.Xaml.Controls.ContentDialog { Title = "未处理异常", Content = "捕获到未处理的异常,应用将继续运行。", PrimaryButtonText = "确定", // 必须指定XamlRoot,否则WinUI3对话框无法正常显示 XamlRoot = currentWindow.Content.XamlRoot }; await errorDialog.ShowAsync(); }); } catch (Exception ex) { Log.Fatal($"处理未处理异常时出错: {ex.Message}"); } } #endif
2. AppDomain级异常处理(非UI线程异常)
对于非UI线程抛出的异常,切换到UI线程显示提示:
private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { try { var exception = e.ExceptionObject as Exception; if (exception == null) return; var crashLog = $"AppDomain Unhandled Exception | Message: {exception.Message} " + $"| Target Method: {exception.TargetSite?.Name} " + $"| Target Class: {exception.TargetSite?.DeclaringType?.FullName}"; Log.Fatal(crashLog, exception); // 跨平台方式切换到UI线程显示弹窗 _ = MainThread.InvokeOnMainThreadAsync(async () => { if (Application.Current.MainPage != null) { await Application.Current.MainPage.DisplayAlert("严重异常", "捕获到严重未处理异常,应用可能需要重启。", "确定"); } }); } catch (Exception ex) { Log.Fatal($"处理AppDomain异常时出错: {ex.Message}"); } }
3. 未观察任务异常处理
针对异步任务中未被捕获的异常:
private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { try { var exception = e.Exception; var crashLog = $"Unobserved Task Exception | Message: {exception.Message} " + $"| Inner Exception: {exception.InnerException?.Message}"; Log.Fatal(crashLog, exception); // 标记异常已被观察,避免应用崩溃 e.SetObserved(); // 跨平台UI线程弹窗 _ = MainThread.InvokeOnMainThreadAsync(async () => { if (Application.Current.MainPage != null) { await Application.Current.MainPage.DisplayAlert("任务异常", "捕获到未观察的任务异常。", "确定"); } }); } catch (Exception ex) { Log.Fatal($"处理任务异常时出错: {ex.Message}"); } }
三、关键注意事项
- 平台编译指令:使用
#if WINDOWS包裹WinUI3特定代码,保证跨平台兼容性。 - UI线程上下文:无论是用WinUI原生
Dispatcher还是MAUI的MainThread.InvokeOnMainThreadAsync,都要确保弹窗代码在UI线程执行。 - XamlRoot指定:WinUI3的
ContentDialog必须指定XamlRoot才能正常显示,否则会抛出上下文异常。 - 跨平台弹窗替代:如果不需要WinUI原生弹窗样式,可直接使用MAUI的
DisplayAlert,代码更简洁且跨平台通用。
内容的提问来源于stack exchange,提问作者Monal
相关产品推荐
相关产品推荐

