WPF中事件处理程序引发的异常被吞没了吗?

4
当我在事件处理程序中抛出异常时,异常处理程序不会被调用?
以下是一个简化示例的示例代码:

App.xaml

<Application x:Class="WpfApplication1.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml"
             DispatcherUnhandledException="App_DispatcherUnhandledException" >
    <Application.Resources/>
</Application>

App.xaml.cs

using System.Windows;
using System.Windows.Threading;

namespace WpfApplication1
{
    public partial class App : Application
    {
        //This method is called when ButtonA is clicked, but not when ButtonB is
        //clicked (and a (random) file is selected ofcourse).
        void App_DispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
        {
            MessageBox.Show(e.Exception.Message, "An exception occurred", MessageBoxButton.OK, MessageBoxImage.Error);
            e.Handled = true;
        }
    }
}

MainWindow.xaml

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="Button A" HorizontalAlignment="Left" Margin="10,10,0,0" VerticalAlignment="Top" Width="75" Click="ButtonA_Click"/>
        <Button Content="Button B" HorizontalAlignment="Left" Margin="90,10,0,0" VerticalAlignment="Top" Width="75" Click="ButtonB_Click"/>
    </Grid>
</Window>

MainWindow.xaml.cs

using Microsoft.Win32;
using System;
using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        private void ButtonA_Click(object sender, RoutedEventArgs e)
        {
            throw new Exception("Works!");
        }

        private void ButtonB_Click(object sender, RoutedEventArgs e)
        {
            var ofd = new OpenFileDialog();
            ofd.FileOk += (s, ce) => {
                throw new Exception("Does not work!?!?");
            };
            ofd.ShowDialog();
        }
    }
}

我已经查看了这个问题这个问题,但是强制使用32位或64位甚至"ANY CPU"都不起作用。另外,当设置这四个(!)处理程序中的任何一个时,在事件抛出异常时没有任何一个处理程序被调用。此外,这篇文章也没有帮助。
我正在运行VS2012(在Win8,x64上),该项目使用.Net Framework 4.5)。我错过了什么?我疯了吗?
为了更清晰:我期望会显示一个消息框(当我点击ButtonA时它确实显示了),或者,实际上,App_DispatcherUnhandledException方法被调用。但是当我点击ButtonB时,该方法没有被调用(因此)消息框也没显示。 ButtonAButtonB之间的唯一区别是"A"中的异常不在事件处理程序中,而"B"中的异常在事件处理程序中。当然,我确保在OpenFileDialog中选择了文件并单击“打开”以选择它。调试器启动并指出抛出了"Does not work!?!?"异常,然后我继续执行,但没有显示任何消息框。

另外:我对WPF非常新,这可能是问题的一部分:P

编辑1

供参考,以下是演示确切问题的两个zip文件:

  1. 简单版 (10Kb)
  2. 扩展版 (10Kb)

在我的计算机上,对于上述两个项目,ButtonA会导致消息框被显示出来,而ButtonB(选择文件后)则不会。即使打开“调试非托管代码”,也是如此。

编辑2

因此,我在另一台计算机上运行了相同的代码,发现以下情况:在另一台计算机上,调试器会显示:

Exception on other machine

请注意标题中的Exception crossed a native/managed boundary。当我尝试恢复执行(继续)时,异常一直弹出。当调试器启动时,我的机器显示:

Exception on my machine

当我恢复执行时,异常会消失在某种黑洞中;主窗体再次显示,什么都没有发生。

这可能与以下设置有关:

Boundary crossed exception settings

然而,即使打开/关闭此选项也没有帮助,即使重新启动VS2012并删除临时文件(以及从项目中的bin / obj目录中),恢复默认值等

所以...我现在知道异常确实与托管和非托管之间的跨界有关。现在我只需要找出如何解决这个问题,以便我可以在FileOk事件中抛出异常(以便最终,我的组件也可以在那里抛出异常)。


你确定异常被抛出了吗?我使用事件处理程序来抛出一个异常来测试我的App_DispatcherUnhandledException,但它被捕获了。尝试直接在事件处理程序中抛出一个异常。 - paparazzo
我正在运行问题中显示的确切代码;正如您所看到的,在“FileOk”事件中抛出了异常。 - RobIII
我觉得我们有一个误解。我在(FileOK(!))事件处理程序中抛出了一个异常。该异常在事件处理程序(MainWindow.xaml.cs)的“第一行”中抛出。(FileOK(!))事件处理程序中没有其他代码。接下来,我希望调用App_DispatcherUnhandledException事件处理程序,因为这是一个未处理的异常。为了演示目的,我只是显示一个消息框,但当然最终还需要做更多的工作(如日志记录等)。问题是App_DispatcherUnhandledException从未被调用,就像它应该被调用一样(我认为)。 - RobIII
3
似乎@Blam删除了一个(或多个?)评论,这就是为什么我似乎在自言自语... - RobIII
有关编程的内容翻译如下:这很重要吗?异常是在托管代码中抛出的吗?我已经为您发布了一个压缩文件,供您查看此处 - RobIII
显示剩余11条评论
2个回答

5

好的,我解决了我的问题。

在谷歌搜索、浏览stackoverflow等网站后,我最终找到了这里这里。现在我明白了FileOk事件是如何在另一个分派程序上处理的,因此解决方案很简单:

private void ButtonB_Click(object sender, RoutedEventArgs e)
{
    var ofd = new OpenFileDialog();
    ofd.FileOk += (s, ce) => {
        this.Dispatcher.BeginInvoke((Action)(() =>
        {
            //We can throw:
            throw new Exception("Yay! This exception is now caught by the UnhandledException handler!");

            //or, alternatively, our component can do work that possibly throws:
            Component.DoFoo();
        }));
    };
    ofd.ShowDialog();
}

这样可以确保异常被传递到正确的调度程序并在那里处理。然后,App_DispatcherUnhandledException方法将被正确调用,我们可以从那里开始处理。


刚想发布这个 :) 很高兴你解决了! - Charleh

-1
如果您按照这样的方式进行连接,您将会看到它正在被处理。
仍然不确定为什么它会逃避App_DispatcherUnhandledException。
using System.ComponentModel;
//using Microsoft.Win32;


namespace UncaughtExceptionHandler
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            //AppDomain currentDomain = AppDomain.CurrentDomain;
            //currentDomain.UnhandledException += new UnhandledExceptionEventHandler(MyHandler);
            InitializeComponent();
        }
        private void ButtonA_Click(object sender, RoutedEventArgs e)
        {
            throw new Exception("WTFA!?!?");
        }
        private void ButtonB_Click(object sender, RoutedEventArgs e)
        {
            System.Windows.Forms.OpenFileDialog ofd = new System.Windows.Forms.OpenFileDialog();
            ofd.FileOk += MyCanelEventHandler;
            //ofd.FileOk += (s, ce) =>
            //{
            //    //MessageBox.Show("Throwikng Exception WTF2!?!?");
            //    throw new Exception("WTF2!?!?");
            //};
            ofd.ShowDialog();
        }
        static void MyCanelEventHandler(Object sender, CancelEventArgs e)
        {
            MessageBox.Show("MyCanelEventHandler");
            throw new Exception("WTFCEH!?!?");
        }
    }
}

调试101是获取调用堆栈。
我在10分钟内获得了一个调用堆栈。

OP做出了三个无效的假设,即使经过长时间的讨论也不理解

  1. 异常被吞噬了
    它没有被吞噬,只是超过了他未捕获的异常处理程序 我的代码证明了这一点,但OP没有遵循
  2. 解决方案没有未管理的代码
    再次错误,OpenFileDialog是未管理的代码
  3. 异常是从托管代码中抛出的
    再次错误
    异常是从未管理代码的回调中抛出的

回调中的第一行
调试101是获取调用堆栈

UncaughtExceptionHandler.exe!UncaughtExceptionHandler.MainWindow.MyCanelEventHandler(object sender,System.ComponentModel.CancelEventArgs e)第55行 C# comdlg32.dll!CFileOpenSave :: _NotifyFileOkChangeCallback()+ 0x18字节 comctl32.dll!_DPA_EnumCallback @ 12()+ 0x20字节 comdlg32.dll!CFileOpenSave :: _NotifyFileOk()+ 0x3d字节 comdlg32.dll!CFileOpenSave :: _CleanupDialog()+ 0x46c2字节 comdlg32.dll!CFileOpenSave :: _HandleOkAndClose()+ 0x3a字节 comdlg32.dll!CFileOpenSave :: _OnCommandMessage()+ 0xf432字节 comdlg32.dll!CFileOpenSave :: s_OpenSaveDlgProc()+ 0x1f42字节 user32.dll!_InternalCallWinProc @ 20()+ 0x23字节 user32.dll!_UserCallDlgProcCheckWow @ 32()+ 0xa9字节 user32.dll!_DefDlgProcWorker @ 20()+ 0x7f字节 user32.dll!_DefDlgProcW @ 16()+ 0x22字节

我正在使用的匿名函数也可以像你在MyCanelEventHandler中所做的那样抛出一个Messagebox,但是实际上,异常没有传递给App_DispatcherUnhandledException(也似乎没有其他捕获未处理异常的方法能够工作)。这正是我的问题所在。你没有在这个答案中改变任何实质性的东西(尽管我很感激你和我一起思考)。此外,我没有看到你的答案“处理”异常,只是抛出它。 - RobIII
你运行了吗?对我来说,与你的解决方案相比,有一个额外的消息框显示异常(“WTFCEH!?!?”),因为.NET运行时将其处理为未处理的异常。虽然这并没有修复它,但它是一个线索。它确实解决了它被吞噬的标题问题。 - paparazzo
我运行了它。我看到了额外的消息框(你看到我的等价物了吗?)。然而,异常没有被捕获;你只是把FileOk事件指向了MyCanelEventHandler(它与取消无关,你也可以称之为Foo)。在这一点上,异常并没有出现在视野中(这是正确的)。异常会在事件处理程序(你的MyCanelEventHandler方法或我的匿名函数)中抛出。在实际情况下,我的组件会在那里抛出异常。但是异常从未被捕获/"无法捕获"-> 这正是我的问题所在。 - RobIII
嗯,俗话说“假设是所有...的根源”,我可能过早地宣称System.Windows.Forms中的OpenFileDialog是Microsoft.Win32中OpenFileDialog的包装器(来源),但是将其中一个更改为另一个也没有帮助;异常仍未传递给所需的异常处理程序。我目前正在浏览此处提供的链接,以查看其中是否有任何指向正确方向的内容。 - RobIII
不幸的是,这些链接似乎只关注主题/外观,而不关注其他(可能存在的)错误。对此感到非常遗憾 :( - RobIII
显示剩余17条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接