5

最近、私はまだ頭を悩ませている問題に遭遇していました。アプリケーションで、ディスパッチャ例外ハンドラを登録しました。同じアプリケーションで、サードパーティコンポーネント(DevExpress Grid Control)により、のイベントハンドラ内で例外が発生しますControl.LayoutUpdated。ディスパッチャの例外ハンドラが1回トリガーされることを期待しています。しかし、代わりに、スタックオーバーフローが発生します。サードパーティのコンポーネントを使用せずにサンプルを作成したところ、すべての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-catchLayoutUpdated。スタックオーバーフローが発生するはずだとは思いません。

4

3 に答える 3

4

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あなたの場合は_フラグが真であることを提案します。

_firePostLayoutEventsfalseに設定される唯一の場所はメソッド内であるため、メソッドの終了前のどこかで例外がスローされ(メソッドを推測します)、このフラグがfalseに設定されない fireAutomationEventsと仮定します。fireAutomationEventsfireLayoutUpdateEvent

ただし、もちろん、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;
}

Oooooh、それは興味深いです-それはUpdateLayout再び呼びかけています-したがって、私はそれがあなたの問題の根本的な原因であるという少し知識のある推測を混乱させます。

ですから、私はそれについてあなたができることはあまりないと思います。

于 2013-01-08T13:00:43.403 に答える
1

次のことについてはよくわかりませんが、正しい方向への推測かもしれません。

MSDNでは次のように述べています。

ただし、LayoutUpdatedは、プロパティの変更、ウィンドウのサイズ変更、明示的な要求(UpdateLayoutまたはApplyTemplate)など、さまざまな理由で、オブジェクトの存続期間中の実行時にも発生する可能性があります。

それで、MessageBoxが表示された後にも起動される可能性がありますか?

その場合、MessageBoxは未処理の例外を介して開き、LayoutUpdated-EventHandlerが発生し、これにより未処理の例外が再び発生します。これにより、無限ループが発生し、しばらくするとスタックオーバーフローが発生します。

LayoutUpdated-EventHandlerで未処理の例外をスローせずに、MessageBox.Show()-メソッドを呼び出すと、無限ループで終了します。これは私の主張を証明するものです。

于 2013-01-08T12:28:59.310 に答える
0

パーティーに4年以上遅れていることに気づきましたが、誰かがこれを便利だと思うかもしれません...

アプリケーションのディスパッチャは、への呼び出し中に入力イベントとレイアウトイベントへの応答を停止しませんMessageBox.Show()。このLayoutUpdatedイベントは、レイアウト関連の作業が行われるたびに発生します。これは、予想よりもはるかに頻繁に発生します。メッセージボックスが表示されている間は起動し続け、エラーをトリガーした条件が続くと、新しい例外が発生し、ハンドラーに表示されるメッセージボックスが増えます。また、MessageBox.Show()はブロッキングコールであるため、戻るまでコールスタックから消えることはありません。後続のハンドラーの呼び出しは、オーバーフローするまで、ディスパッチャーの呼び出しスタックにどんどん深くプッシュされます。

あなたは本当に2つの別々の問題を抱えています:

  1. クラッシュダイアログを表示するコードは再入可能です。

  2. クラッシュダイアログが表示されている間、アプリケーションはディスパッチャスレッドで例外を発生させ続けます。

非再入可能なキューで最初の問題を解決できます。クラッシュダイアログをすぐに表示するのではなく、ハンドラーに例外をキューに入れてもらいます。まだスタックの上位でキューを処理していない場合にのみ、ハンドラーにキューを処理させます。これにより、複数のクラッシュダイアログが同時に表示されるのを防ぎ、コールスタックが深くなりすぎないようにして、スタックオーバーフローの問題を回避する必要があります。

2番目の問題に対処するには、最初の例外が表示されたらすぐに(クラッシュダイアログを表示する前に)、アプリケーションの問題のある部分をシャットダウンする必要があります。または、重複する例外を除外して、同等のエラーが同時にキューに入れられないようにする方法を考案することもできます。しかし、例外がどれほど急速に繰り返されているかを考えると、最初のオプションを選択します。

これらの問題の両方に対処する必要があることに注意してください。2番目の問題に対処しないと、と引き換えになってしまう可能性がありStackOverflowExceptionますOutOfMemoryException。または、無限の数のクラッシュダイアログを次々に表示します。いずれにせよ、それは悪いでしょう。

于 2017-06-20T20:01:38.023 に答える