Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Debug.Assert() has unexpected side effects - searching for alternatives

Tags:

c#

debugging

wpf

I'm frequently using asserts to detect unexpected program states. I thought an assert is a conditional message box the immediately stops all threads so that (on pressing "Retry") I can inspect the current application state.

This is not the case! While the assert message is open, my wpf application continues processing events. It is absurd, as on breaking into the debugger the situation might be totally different compared to what the assert "saw" initially. You can have the case that the check for the assert to fire changes through the assert itself, you can have recursive execution of methods - with the consequence of multiple asserts or states in which the program would never come normally.

As far as I understand the assert-function, this is a problem by design. The dialog runs on the same GUI thread as the application itself and hence needs to process messages for its own purpose. But this often has the described side-effects.

So I'm searching for an assert alternative that fulfills the requirement to stop all running threads when invoked. As workaround, I sometimes use "Debugger.Break();" which has (unfortunately) no effect if started without debugger.

For illustrating the problem, please see the following code snipped that in the most simplified manner produces some phenomenons:

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

On running the code, watch the output panel of the developer studio. You will see the numbers go up to 5, then the assert fires. But while the dialog is open, the numbers are still increasing. Hence the condition of the assert changes while the assert is open! Now check the main window –it’s still responsive. Set a breakpoint at “base.OnLocationChanged(e);“ and move the main window => you will hit the break point. But mind the callstack:

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

This clearly shows that arbitrary code can be executed while the assert is open.

So I'm searching for an assert like mechanism that stopps all existing threads and runns in it's own (thread-) context. Any ideas?

like image 700
Michael Podlejska Avatar asked Aug 04 '13 15:08

Michael Podlejska


2 Answers

You are finding out more about how the dispatcher loop works. And yes, the MessageBox that the default trace listener uses to report the failure does not do much to stop your program. It was designed to stop the user, it is a modal dialog box that disables all user input. But does not stop anything that you do in your code. Like it calling Dispatcher.BeginInvoke().

You will need another implementation of the TraceListener.Fail() method. That's quite possible, edit your App.xaml.cs file and make it look similar to this:

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

The code works by removing the DefaultTraceListener, the one that's giving you a headache, from the installed listeners. And adds a custom one, the MyListener class. Which doesn't do much, just uses the original listener to get messages displayed in the Output window. But with an override for the Fail() message, it automatically triggers a debugger break. Just what you want here.

like image 177
Hans Passant Avatar answered Oct 04 '22 20:10

Hans Passant


I'm answering my own question as summary and extension of the provided information by Jon Skeet and Hans Passant:

For the case the program runs in the debugger the option with Debugger.Break() or enabling the EEMessageException is for me the way to go. Both methods immediately stop all threads.

If not debugging and the assert occurs in the GUI thread a message box running on a separate thread helps (see http://eprystupa.wordpress.com/2008/07/28/running-wpf-application-with-multiple-ui-threads/)

Here is the code putting all together (by extending the suggestion from 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);
        }
      }
    }
  }
like image 43
Michael Podlejska Avatar answered Oct 04 '22 21:10

Michael Podlejska