为什么Environment.Exit()不再终止程序?

147

我最近刚刚发现了这个问题,我从这个问题得到了验证,证实这不仅仅局限于我的电脑。

最简单的重现方法是启动一个Windows Forms应用程序,添加一个按钮,并编写以下代码:

    private void button1_Click(object sender, EventArgs e) {
        MessageBox.Show("yada");
        Environment.Exit(1);         // Kaboom!
    }

程序在执行Exit()语句后失败。在Windows表单中,你会收到“创建窗口句柄错误”的报错信息。
启用非托管调试使情况有所好转。COM模态循环正在执行并允许传递WM_PAINT消息。这对于Disposed(已释放)的窗体来说是致命的。
到目前为止,我收集到的唯一事实是:
  • 这不仅仅限于在调试器中运行。没有调试器时也会失败,而且表现得相当糟糕,WER崩溃对话框出现了两次。
  • 与进程的位数无关。wow64层非常臭名昭著,但是AnyCPU版本的程序也会以同样的方式崩溃。
  • 与.NET版本无关,4.5和3.5以同样的方式崩溃。
  • 退出代码并不重要。
  • 在调用Exit()之前调用Thread.Sleep()不能修复它。
  • 这在Windows 8的64位版本上发生,而Windows 7似乎没有受到同样的影响。
  • 这应该是相对较新的行为,我以前没有见过这种情况。尽管我的电脑上更新历史记录不准确,但我没有看到有任何相关的Windows Update更新发布。
  • 这是极其糟糕的行为。你会在AppDomain.UnhandledException事件处理程序中编写类似这样的代码,而它会以同样的方式崩溃。
我特别关心如何避免程序崩溃,尤其是AppDomain.UnhandledException的情况让我很困惑;.NET程序终止的方法并不多。请注意,在UnhandledException事件处理程序中调用Application.Exit()或Form.Close()都是无效的,因此它们都不是解决方法。保留HTML标签。

更新:Mehrdad指出终结器线程可能是问题的一部分。我认为我看到了这一点,并且还看到了CLR给终结器线程的2秒超时的一些证据。

终结器在NativeWindow.ForceExitMessageLoop()内部。那里有一个IsWindow() Win32函数,大致对应于代码位置,在32位模式下查看机器代码时偏移量为0x3c。似乎IsWindow()发生了死锁。然而,我无法获得内部的良好堆栈跟踪,调试器认为P/Invoke调用刚刚返回。这很难解释。如果您能获得更好的堆栈跟踪,我很想看看。我的:

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

ForceExitMessageLoop调用之上没有任何内容,启用了非托管调试器。


2
我刚刚在.NET 4、4客户端框架、3.5、3.5客户端框架、3.0和2.0上尝试了这个,没有在任何一个版本上收到错误。我的操作系统是64位的Windows 7,使用的是VS2010。 - Steve
2
@Steve 这是在64位版本的Windows 8上发生的 Hans已经这么说了! - Parimal Raj
7
我可以重现这个问题(在64位的Windows 8系统上),我复制并粘贴了你的代码,并连接了一个按钮,出现了描述中完全相同的症状。 - keyboardP
3
在控制台模式下,无法演示这个问题,当Exit()不断发送消息时,不会出现任何问题。 - Hans Passant
3
我之前在一些64位Win7系统中遇到了使用Exit(0)的这种行为,更改ExitCode并没有帮助。现在我改用Process.GetCurrentProcess().Kill(),没有任何问题,可以正常工作。 - Sriram Sakthivel
显示剩余18条评论
4个回答

94

我联系了微软以解决这个问题,结果好像有所进展:)虽然他们没有回复我确认是否解决了问题,但是Windows组很难直接联系到,并且我不得不使用中间人。

通过Windows更新提供的更新解决了这个问题。显然在崩溃之前的明显2秒延迟已经消失,这强烈表明IsWindow()死锁已得到解决。而且程序能够干净、可靠地关闭。该更新安装了Windows Defender、wdboot.sys、wdfilter.sys、tcpip.sys、rpcrt4.dll、uxtheme.dll、crypt32.dll和wintrust.dll的补丁。

Uxtheme.dll是一个异常的文件,它实现了视觉样式主题API,并被此测试程序使用。虽然我不能确定,但我觉得它可能是问题的源头。在我的机器上,C:\WINDOWS\system32目录中的副本版本号为6.2.9200.16660,创建于2013年8月14日。

案子结案了。


12
我的电脑上的Windows更新历史记录不再准确。 我只知道它是在8月14日安装的。 - Hans Passant

56

我不知道为什么它不再起作用了,但我认为Environment.Exit会执行未决定的终止程序,而Environment.FailFast则不会。

可能是因为(出于某种离奇的原因)您有一些奇怪的未决定的终止程序必须在之后运行,从而导致发生这种情况。


2
你可能有所发现。Finalizer正在忙于执行NativeWindow.ForceExitMessageLoop()。奇怪的是它没有嵌套在任何调用中。 - Hans Passant
@HansPassant:我希望我能够重现此问题以便调查,但我无法做到。 调用NativeWindow.ForceExitMessageLoop是否被卡在托管或非托管代码中? 它是否被卡住了,还是忙于等待消息或其他什么东西? - user541686
@HansPassant:是的,这台机器上启用了Windows更新。我安装的最新的重启更新是从2013年7月16日;之后我只有病毒定义更新(最新的是昨天)。 - user541686
4
针对未处理异常的情况,我认为Environment.FailFast()方法可能是最好的方法,但有很多已经存在的代码使用了Environment.Exit(),这将导致不太友好的崩溃:((我之前不知道有FailFast方法,谢谢您提供的信息!) - Ian Yates
2
你肯定有所发现。在我的情况下,我使用IHost.StartAsync启动了一个IHost来执行一些集成测试,但是在调用(当然要等待)IHost.StopAsync之后,进程仍然没有终止。只有在调用IHost.Dispose之后,进程才会终止。感谢您的提示。 - Malte R
显示剩余6条评论

6

虽然这不解释它为什么会发生,但是我不会像你的示例中那样在按钮事件处理程序中调用 Environment.Exit,而是像 rene 的回答 建议的那样关闭主窗体。

至于 AppDomain.UnhandledException 处理程序,也许您可以只设置 Environment.ExitCode 而非调用 Environment.Exit

我不确定您试图在这里实现什么。为什么您要从 Windows Forms 应用程序返回退出代码?通常,退出代码是由控制台应用程序使用的。

我特别想知道您可能做些什么来避免这种崩溃 调用 Environment.Exit() 是必需的,以防止 WER 对话框显示。

您的 Main 方法中是否有 try/catch 语句?对于 Windows Forms 应用程序,我总是在消息循环和未处理的异常处理程序周围加上 try/catch 语句。


很确定你应该调用 Application.Exit 而不是 Environment.Exit - user541686
8
抱歉,这不是一个解决方法。必须调用Environment.Exit()以防止显示WER对话框。还要注意的是,“已知事实”,退出代码并不重要。 - Hans Passant
8
@Hans:在第一时间捕获AppDomain.UnhandledException以尝试避免WER对话框,这是合法的吗?我的意思是,如果有一个未处理的异常,WER对话框 应该 显示出来,不是吗?答案:捕获AppDomain.UnhandledException以避免WER对话框显示并不符合预期行为。WER对话框旨在帮助诊断和解决问题,因此应该允许它正常工作。 - Harry Johnston

4
我在我们的应用程序中发现了同样的问题,我们采用以下结构解决了这个问题:
Environment.ExitCode=1;
Application.Exit();

1
一般来说,与 Environment.Exit() 相比,在调用 Application.Exit() 后立即终止应用程序的可能性较小,因为它在退出之前会执行更多的工作。 - FindOutIslamNow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接