This is something I discovered just a few days ago, I got confirmation that it isn't just limited to my machine from this question.
The easiest way to repro it is by starting a Windows Forms application, add a button and write this code:
private void button1_Click(object sender, EventArgs e) {
MessageBox.Show("yada");
Environment.Exit(1); // Kaboom!
}
The program fails after the Exit() statement executes. On Windows Forms you get "Error creating window handle".
Enabling unmanaged debugging makes it somewhat clear what's going on. The COM modal loop is executing and allows a WM_PAINT message to be delivered. That's fatal on a disposed form.
The only facts I've gathered so far are:
- It isn't just limited to running with the debugger. This also fails without one. Rather poorly as well, the WER crash dialog shows up twice.
- It doesn't have anything to do with the bitness of the process. The wow64 layer is pretty notorious, but an AnyCPU build crashes the same way.
- It doesn't have anything to do with the .NET version, 4.5 and 3.5 crash the same way.
- The exit code doesn't matter.
- Calling Thread.Sleep() before calling Exit() doesn't fix it.
- This happens on the 64-bit version of Windows 8, and Windows 7 does not seem to be affected the same way.
- This should be relatively new behavior, I haven't seen this before. I see no relevant updates delivered through Windows Update, albeit that the update history isn't accurate on my machine any more.
- This is grossly breaking behavior. You would write code like this in an event handler for AppDomain.UnhandledException, and it crashes the same way.
I'm particularly interested in what you could possibly do to avoid this crash. Particularly the AppDomain.UnhandledException scenario stumps me; there are not a lot of ways to terminate a .NET program. Please do note that calling Application.Exit() or Form.Close() are not valid in an event handler for UnhandledException, so they are not workarounds.
UPDATE: Mehrdad pointed out that the finalizer thread could be part of the problem. I think I'm seeing this and am also seeing some evidence for the 2 second timeout that the CLR gives the finalizer thread to finish executing.
The finalizer is inside NativeWindow.ForceExitMessageLoop(). There's an IsWindow() Win32 function there that roughly corresponds with the code location, offset 0x3c when looking at the machine code in 32-bit mode. It seems that IsWindow() is deadlocking. I cannot get a good stack trace for the internals however, the debugger thinks the P/Invoke call just returned. This is hard to explain. If you can get a better stack trace then I'd love to see it. Mine:
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.ForceExitMessageLoop() + 0x3c bytes
System.Windows.Forms.dll!System.Windows.Forms.NativeWindow.Finalize() + 0x16 bytes
[Native to Managed Transition]
kernel32.dll!@BaseThreadInitThunk@12() + 0xe bytes
ntdll.dll!___RtlUserThreadStart@8() + 0x27 bytes
ntdll.dll!__RtlUserThreadStart@8() + 0x1b bytes
Nothing above the ForceExitMessageLoop call, unmanaged debugger enabled.
See Question&Answers more detail:
os 与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…