Note that there are some explanatory texts on larger screens.

plurals
  1. PO
    text
    copied!<p><strong>tl;dr: It's the debugger</strong>. Detach and you won't get this weird behaviour.</p> <hr> <p>Alright. I did some experimentation with a Brand Spankin' New Project, and came up with a result. I'll start by posting the code so that you, too, can join in the fun and see it firsthand.</p> <h1>Teh Codez</h1> <p><em><a href="http://thedailywtf.com/Articles/plz-email-me-teh-codez.aspx" rel="noreferrer">plz email them to me</a></em> (unrelated)</p> <h3>Form1.cs</h3> <p><img src="https://i.imgur.com/PjGeg.png" alt="Example form"><br> You will need two buttons on the form. Caption them appropriately so that it's blindingly obvious what you're doing.</p> <pre><code>public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { throw new InvalidOperationException("Exception thrown from UI thread"); } private void button2_Click(object sender, EventArgs e) { new Thread(new ThreadStart(ThrowThreadStart)).Start(); } private static void ThrowThreadStart() { throw new InvalidOperationException("Exception thrown from background thread"); } } </code></pre> <h3>Program.cs</h3> <pre><code>static class Program { [STAThread] static void Main() { AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException; Application.ThreadException += Application_ThreadException; Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } static void Application_ThreadException(object sender, ThreadExceptionEventArgs e) { if (e.Exception != null) { MessageBox.Show(string.Format("+++ Application.ThreadException: {0}", e.Exception.Message)); } else { MessageBox.Show("Thread exception event fired, but object was not an exception"); } } static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e) { Exception ex = e.ExceptionObject as Exception; if (ex != null) { MessageBox.Show(string.Format("*** AppDomain.UnhandledException: {0}", ex.Message)); } else { MessageBox.Show("Unhandled exception event fired, but object was not an exception"); } } } </code></pre> <h3>The project file</h3> <p><em>Disable the hosting process</em>, otherwise the AppDomain (and Forms itself) won't be unloaded between debugging sessions, which will make the line <code>Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException, false);</code> throw an <code>InvalidOperationException</code> if you change the <code>UnhandledExceptionMode</code> argument between runs. <em>EDIT: or at all, if set to CatchException</em></p> <p><strike>It's not strictly necessary for this investigation, but if you're going to play around and change the <code>UnhandledExceptionMode</code> -- which I expect you probably will if you're running this code yourself --</strike> this setting will save you heartache. <img src="https://i.imgur.com/KBQQt.png" alt="Disable the hosting process"></p> <h1>The Testing</h1> <h2>Inside the debugger</h2> <h3>Throw in the UI thread</h3> <ol> <li>Click <em>Throw in UI</em></li> <li>Get "unhandled exception" helper in the debugger</li> <li><code>F5</code> to continue execution</li> <li>Dialog will show, indicating that the Application handler received an exception event from the UI thread</li> <li>Click OK</li> <li>Application does not crash, so feel free to rinse and repeat.</li> </ol> <h3>Throw in a background thread</h3> <ol> <li>Click <em>Throw in background</em></li> <li>Dialog will show, indicating that the AppDomain handler received an exception event from a background thread</li> <li>Get "unhandled exception" helper in the debugger</li> <li><code>F5</code> to continue execution</li> <li><code>goto 2</code>. Really.</li> </ol> <p>It seems here that the AppDomain handler trumps the debugger for whatever reason. After the AppDomain is done with it, however, the debugger does manage to pick up on the unhandled exception. But what happens next is puzzling: the AppDomain handler gets run again. And again. And again, <em>ad infinitum</em>. Putting a breakpoint in the handler indicates that that isn't recursive (at least, not within .NET) so this <em>probably</em> won't end in a stack overflow. <img src="https://i.imgur.com/y7TQB.png" alt="The background thread"></p> <p>Now, let's try it...</p> <h2>Outside the debugger</h2> <h3>UI thread</h3> <p>Same procedure, same result -- except of course that the debugger helper was conspicuously absent. The program still didn't crash, because <code>UnhandledExceptionMode.CatchException</code> does what it says it will do, and handles the exception "internally" (within Forms, at least) instead of escalating it to the <strike>Feds</strike> AppDomain.</p> <h3>Background thread</h3> <p>Now this is interesting.</p> <ol> <li>Click <em>Throw in background</em></li> <li>Dialog will show, indicating that the AppDomain handler received an exception event from a background thread</li> <li>Get Windows crash dialog<br> <img src="https://i.imgur.com/MYjhd.png" alt="Windows crash dialog"></li> <li>Click <code>Debug</code> to snatch the exception with JIT debugging</li> <li>Get "unhandled exception" helper</li> <li><code>F5</code> to continue</li> <li><strong>Program exits</strong></li> </ol> <p>Firstly, the AppDomain doesn't go into loops like it does with the debugger attached, and secondly, attaching the debugger just-in-time from the Windows error dialog <strong>does not</strong> trigger this weird behaviour.</p> <h1>Conclusion</h1> <p>It seems like the debugger does something weird regarding unhandled exceptions making their way to the AppDomain. I don't know much about how the debugger does its magic, so I won't speculate, but the loop <strong>only</strong> occurs when the debugger is attached, so if the loop is the problem, detaching is one workaround you could use (perhaps using <code>Debugger.Launch()</code> to give yourself time to reattach later if you require it).</p> <p>&lt;3</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload