5

我经常使用断言来检测意外的程序状态。我认为断言是一个条件消息框,它会立即停止所有线程,以便(在按下“重试”时)我可以检查当前的应用程序状态。

不是这种情况!当断言消息打开时,我的 wpf 应用程序继续处理事件。这是荒谬的,因为在闯入调试器时,情况可能与断言最初“看到”的情况完全不同。您可以通过断言本身检查断言触发更改的情况,您可以递归执行方法 - 结果是多个断言或程序永远不会正常进入的状态。

据我了解断言功能,这是一个设计问题。该对话框与应用程序本身在同一个 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 回答