You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 16:04:53