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.
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With