最近,我遇到了一个问题,但仍然无法解决。在一个应用程序中,我注册了一个调度程序异常处理程序。在同一应用程序中,第三方组件(DevExpress Grid Control)在Control.LayoutUpdated的事件处理程序中导致异常。我希望调度程序异常处理程序被触发一次。但是,相反,我得到了堆栈溢出。我制作了一个没有第三方组件的示例,并发现该示例在每个WPF应用程序中都会发生。

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

    namespace MyApplication
    {
        /* App.xaml

            <Application
                xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                x:Class="MyApplication.App"
                Startup="OnStartup"
            />

        */
        public partial class App
        {
            private void OnStartup(object sender, StartupEventArgs e)
            {
                DispatcherUnhandledException += OnDispatcherUnhandledException;
                MainWindow = new MainWindow();
                MainWindow.Show();
            }
            private static void OnDispatcherUnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
            {
                MessageBox.Show(e.Exception.Message);
                e.Handled = true;
            }
        }

        public class MainWindow : Window
        {
            private readonly Control mControl;

            public MainWindow()
            {
                var grid = new Grid();
                var button = new Button();

                button.Content = "Crash!";
                button.HorizontalAlignment = HorizontalAlignment.Center;
                button.VerticalAlignment = VerticalAlignment.Center;
                button.Click += OnButtonClick;

                mControl = new Control();

                grid.Children.Add(mControl);
                grid.Children.Add(button);

                Content = grid;
            }

            private void OnButtonClick(object sender, RoutedEventArgs e)
            {
                mControl.LayoutUpdated += ThrowException;
                mControl.UpdateLayout();
                mControl.LayoutUpdated -= ThrowException;
            }

            private void ThrowException(object sender, EventArgs e)
            {
                throw new NotSupportedException();
            }
        }
    }

有什么办法可以防止这种行为? .NET Framework 3.0、3.5、4.0和4.5会发生这种情况。我不能只是将try-catch包裹在LayoutUpdated事件处理程序中,因为它位于第三方组件中,而且我不认为应该发生堆栈溢出。

最佳答案

我认为Florian GI关于消息框是正确的,但是如果您在Handled方法中执行了其他操作(或者没有做任何事情,或者只是将true设置为OnDispatcherUnhandledException而不是消息框),它仍然会永远循环并且不会到达mControl.LayoutUpdated -= ThrowException;行。

所以我想我会通过dotPeek窥探一下代码...

当您在控件上调用UpdateLayout时,最终它会到达ContextLayoutManager.UpdateLayout方法,此方法的代码段如下所示:

// ... some code I snipped
bool flag2 = true;
UIElement element = (UIElement) null;
try
{
    this.invalidateTreeIfRecovering();
    while (this.hasDirtiness || this._firePostLayoutEvents)
    {

        //... Loads of code that I think will make sure
        // hasDirtiness is false (since there is no reason
        // for anything remaining dirty - also the event is
        // raised so I think this is a safe assumption

        if (!this.hasDirtiness)
        {
          this.fireLayoutUpdateEvent();
          if (!this.hasDirtiness)
          {
            this.fireAutomationEvents();
            if (!this.hasDirtiness)
              this.fireSizeChangedEvents();
          }
        }
        //... a bit more
        flag2 = false;
    }
}
finally
{
    this._isUpdating = false;
    this._layoutRequestPosted = false;
    //... some more code
    if (flag2)
    {
       //... some code that I can't be bothered to grok
      this.Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, (Delegate) ContextLayoutManager._updateLayoutBackground, (object) this);
    }
}

// ... and for good measure a smidge more code

我将平底船,并建议_ firePostLayoutEvents标志在您的情况下为true。
_firePostLayoutEvents设置为false的唯一地方是fireAutomationEvents方法,因此我们假设在fireAutomationEvents方法结束之前的某个地方引发了您的异常(我想是fireLayoutUpdateEvent方法),因此此标志不会设置为false。

但是,当然,finally在循环之外,因此它不会永远循环(如果这样做,则不会获得StackOverflowException)。

对,向前,因此我们正在调用UpdateLayoutBackground函数,该函数实际上只是调用NeedsRecalc,因此让我们看一下...
private void NeedsRecalc()
{
  if (this._layoutRequestPosted || this._isUpdating)
    return;
  MediaContext.From(this.Dispatcher).BeginInvokeOnRender(ContextLayoutManager._updateCallback, (object) this);
  this._layoutRequestPosted = true;
}

Rightyho,正在调用UpdateLayoutCallback,所以在那儿着眼睛...
private static object UpdateLayoutCallback(object arg)
{
  ContextLayoutManager contextLayoutManager = arg as ContextLayoutManager;
  if (contextLayoutManager != null)
    contextLayoutManager.UpdateLayout();
  return (object) null;
}

噢,这很有趣-它再次调用UpdateLayout-因此,我会怀疑一个稍微受过教育的猜测,这是问题的根本原因。

因此,恐怕我对此无能为力。

关于c# - 如果已注册DispatcherUnhandledException,则Control.LayoutUpdated中的异常之后的堆栈溢出,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/14211306/

10-17 01:15