Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Stack overflow after an exception in Control.LayoutUpdated if DispatcherUnhandledException is registered

Tags:

c#

wpf

Recently, I was running into a problem which I'm still breaking my head over. In an application, I registered a dispatcher exception handler. In the same application, a third-party-component (DevExpress Grid Control) causes an exception within the event handler for Control.LayoutUpdated. I expect, that the dispatcher exception handler is triggered once. But instead, I get a stack overflow. I produced a sample without the third party component and discovered, that it happens in every WPF application.

    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();
            }
        }
    }

Is there any way to prevent this behavior? It happens with .NET framework 3.0, 3.5, 4.0 and 4.5. I can't just wrap a try-catch around the LayoutUpdated event handler since it is in a third party component and I don't think, a stack overflow should happen.

like image 732
Georg Avatar asked Jan 08 '13 08:01

Georg


2 Answers

I think Florian GI is right about the message box, but if instead of a message box you did something else (or nothing i.e. just set Handled to true) in the OnDispatcherUnhandledException method it still loops forever and doesn't get to the mControl.LayoutUpdated -= ThrowException; line.

So I thought I would have a little snop through the code with dotPeek...

When you call UpdateLayout on the control, ultimately it gets to the method ContextLayoutManager.UpdateLayout and a snippet of this method looks like this:

// ... 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

I'm going to take a punt and suggest that the _firePostLayoutEvents flag is true in your case.

The only place where _firePostLayoutEvents is set to false is in the fireAutomationEvents method so let's assume that somewhere before the end of the fireAutomationEvents method your exception is thrown (I would guess the fireLayoutUpdateEvent method) so this flag will not get set to false.

But, of course, the finally is outside the loop so it will not loop forever (and if it did you'd not get a StackOverflowException).

Right, onward, so we are invoking the UpdateLayoutBackground function, which actually just calls NeedsRecalc so let's look at that...

private void NeedsRecalc()
{
  if (this._layoutRequestPosted || this._isUpdating)
    return;
  MediaContext.From(this.Dispatcher).BeginInvokeOnRender(ContextLayoutManager._updateCallback, (object) this);
  this._layoutRequestPosted = true;
}

Rightyho, that is calling the UpdateLayoutCallback so squinting at that...

private static object UpdateLayoutCallback(object arg)
{
  ContextLayoutManager contextLayoutManager = arg as ContextLayoutManager;
  if (contextLayoutManager != null)
    contextLayoutManager.UpdateLayout();
  return (object) null;
}

Oooooh, that is interesting - it's calling UpdateLayout again - I would, therefore, hazzard a slightly educated guess that that is the root cause of your problem.

Therefore, I don't think there is anything much you can do about it I am afraid.

like image 130
kmp Avatar answered Oct 24 '22 08:10

kmp


I am not quite sure about the following, but maybe it's a guess in the right direction.

In MSDN it sais:

However, LayoutUpdated can also occur at run time during the object lifetime, for a variety of reasons: a property change, a window resizing, or an explicit request (UpdateLayout or ApplyTemplate).

So it might get also fired after a MessageBox has shown?

If thats the case a MessageBox would open through an unhandled exception, which fires the LayoutUpdated-EventHandler and this again raises an unhandled exception. This would lead to an endless loop and after a while to the stack overflow.

If you do not throw an unhandled exception in the LayoutUpdated-EventHandler, but call the MessageBox.Show()-method it also ends in an endless loop, what would proof my point.

like image 42
Florian Gl Avatar answered Oct 24 '22 08:10

Florian Gl