5

予期しないプログラムの状態を検出するためにアサートを頻繁に使用しています。アサートは、(「再試行」を押すと) 現在のアプリケーションの状態を調べることができるように、すべてのスレッドをすぐに停止する条件付きメッセージ ボックスであると考えました。

これはそうではありません!アサート メッセージが開いている間、wpf アプリケーションはイベントの処理を続けます。デバッガーに侵入すると、アサートが最初に「見た」ものと比較して状況がまったく異なる可能性があるため、それはばかげています。アサート自体を介してアサートを起動するためのチェックが変更される場合があり、メソッドを再帰的に実行することができます-複数のアサートまたはプログラムが決して正常にならない状態の結果。

私が assert-function を理解している限り、これは設計上の問題です。ダイアログは、アプリケーション自体と同じ GUI スレッドで実行されるため、独自の目的のためにメッセージを処理する必要があります。しかし、これには多くの場合、説明されている副作用があります。

そのため、呼び出されたときに実行中のすべてのスレッドを停止するという要件を満たすアサートの代替手段を探しています。回避策として、「Debugger.Break();」を使用することがあります。デバッガーなしで開始した場合、(残念ながら) 効果はありません。

問題を説明するために、最も単純化された方法でいくつかの現象を生成する次のコードを抜粋して参照してください。

public partial class MainWindow : Window
{
  int _count = 0;

  public MainWindow()
  {
    InitializeComponent();
  }    
  private void onLoaded(object sender, RoutedEventArgs e)
  {
    test(); 
  }
  protected override void OnLocationChanged(EventArgs e)
  {
    base.OnLocationChanged(e);
  }    
  void test()
  {
    ++_count;
    Dispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, new Action(() =>
    {
      test();
    }));

    Trace.TraceInformation(_count.ToString());
    Debug.Assert(_count != 5);
  }
}

コードを実行すると、開発者スタジオの出力パネルが表示されます。数字が 5 まで上がり、アサートが発火するのがわかります。しかし、ダイアログが開いている間、数はまだ増加しています。したがって、アサートが開いている間にアサートの状態が変化します。ここで、メイン ウィンドウを確認します。まだ応答があります。「base.OnLocationChanged(e);」にブレークポイントを設定し、メイン ウィンドウを移動 => ブレーク ポイントにヒットします。ただし、コールスタックに注意してください。

MainWindow.OnLocationChanged(System.EventArgs e)    
(…)    
System.dll!Microsoft.Win32.SafeNativeMethods.MessageBox(System.IntPtr 
System.dll!System.Diagnostics.AssertWrapper.ShowMessageBoxAssert(stri
System.dll!System.Diagnostics.DefaultTraceListener.Fail(string message, str 
System.dll!System.Diagnostics.DefaultTraceListener.Fail(string message)
System.dll!System.Diagnostics.TraceInternal.Fail(string message)
System.dll!System.Diagnostics.Debug.Assert(bool condition)
MainWindow.test()
MainWindow.test.AnonymousMethod__0()

これは、アサートが開いている間に任意のコードを実行できることを明確に示しています。

したがって、既存のすべてのスレッドを停止し、独自の (スレッド) コンテキストで実行するアサートのようなメカニズムを探しています。何か案は?

4

2 に答える 2

4

ディスパッチャー ループがどのように機能するかについて、さらに詳しく知ることができます。そして、はい、デフォルトのトレース リスナーが失敗を報告するために使用する MessageBox は、プログラムを停止させることはほとんどありません。ユーザーを停止するように設計されており、すべてのユーザー入力を無効にするモーダル ダイアログ ボックスです。ただし、コードで行うことは何も停止しません。Dispatcher.BeginInvoke() を呼び出すように。

TraceListener.Fail() メソッドの別の実装が必要になります。App.xaml.cs ファイルを編集して、次のようにします。

using System.Diagnostics;
...
    public partial class App : Application {
        public App() {
            if (Debugger.IsAttached) {
                var def = Debug.Listeners["Default"];
                Debug.Listeners.Remove(def);
                Debug.Listeners.Add(new MyListener(def));
            }
        }

        private class MyListener : TraceListener {
            private TraceListener defListener;
            public MyListener(TraceListener def) { defListener = def; }
            public override void Write(string message) { defListener.Write(message); }
            public override void WriteLine(string message) { defListener.WriteLine(message); }

            public override void Fail(string message, string detailMessage) {
                base.Fail(message, detailMessage);
                Debugger.Break();
            }
        }
    }

このコードは、インストールされたリスナーから、頭痛の種である DefaultTraceListener を削除することで機能します。そして、カスタムの MyListener クラスを追加します。元のリスナーを使用して、出力ウィンドウに表示されるメッセージを取得するだけです。ただし、Fail() メッセージをオーバーライドすると、デバッガー ブレークが自動的にトリガーされます。ここで欲しいものだけ。

于 2013-08-04T21:10:19.493 に答える
2

Jon Skeet と Hans Passant によって提供された情報の要約と拡張として、私自身の質問に答えています。

プログラムがデバッガーで実行される場合、Debugger.Break() を使用するオプションまたは EEMessageException を有効にするのが私の方法です。どちらの方法も、すべてのスレッドを即座に停止します。

デバッグを行わず、GUI スレッドでアサートが発生した場合は、別のスレッドで実行されているメッセージ ボックスが役立ちます ( http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-を参照)。スレッド/ )

これがすべてをまとめたコードです(Hans Passantからの提案を拡張することにより)

  public partial class App : Application
  {
    public App()
    {
      var def = Debug.Listeners["Default"];
      Debug.Listeners.Remove(def);
      Debug.Listeners.Add(new MyListener(def, Dispatcher.CurrentDispatcher));
    }

    private class MyListener : TraceListener
    {
      private TraceListener _defListener;
      private Dispatcher _guiDisp;
      public MyListener(TraceListener def, Dispatcher guiDisp) 
      { 
        _defListener = def;
        _guiDisp = guiDisp;
      }
      public override void Write(string message) { _defListener.Write(message); }
      public override void WriteLine(string message) { _defListener.WriteLine(message); }

      public override void Fail(string message, string detailMessage)
      {
        base.Fail(message, detailMessage);  //write message to the output panel

        if (Debugger.IsAttached)
        {
          //if debugger is attached, just break => all threads stopped
          Debugger.Break();
        }
        else if (Dispatcher.CurrentDispatcher == _guiDisp)
        {
          //running standalone and called in the GUI thread => block it
          Thread anotherGuiThread = new Thread(() =>
          {
            //TODO: nice dlg with buttons
            var assertDlg = new Window() { Width = 100, Height = 100 };
            assertDlg.Show();
            assertDlg.Closed += (s, e) => assertDlg.Dispatcher.InvokeShutdown();
            System.Windows.Threading.Dispatcher.Run();  //run on its own thread
          });

          anotherGuiThread.SetApartmentState(ApartmentState.STA);
          anotherGuiThread.Start();
          anotherGuiThread.Join();
        }
        else
        {
          //running standalone and NOT called in the GUI thread => call normal assert
          _defListener.Fail(message, detailMessage);
        }
      }
    }
  }
于 2013-08-06T21:08:33.133 に答える