SEHException未被Try/Catch捕获

16

我的应用程序会在后台线程中定期检查一个网络文件夹(UNC路径)是否有应用程序更新。它会读取文件的程序集版本,就像这样:

Try
    newVers = System.Reflection.AssemblyName.GetAssemblyName("\\server\app.exe").Version
Catch ex As Exception
    ' ignore
End Try

这段代码经常被执行,我猜测在多个客户网站上总共执行了超过十万次而没有问题。

有时候,GetAssemblyName 会引发 FileNotFoundException 异常,例如当网络文件夹无法访问时(可能会发生,并需要处理)。该异常会被下面的 Catch 块捕获,一切都正常工作。

然而,在三个已报告的情况下,GetAssemblyName 调用引发了一个 SEHException。奇怪的是,该异常并未被下面的 Catch 块捕获,但被我的全局未处理异常处理程序 (System.AppDomain.CurrentDomain.UnhandledException) 捕获。结果,应用程序崩溃了。

以下是异常详细信息(不幸的是,异常的 ErrorCodeCanResume 字段未被我的错误处理例程记录):

Caught exception: System.Runtime.InteropServices.SEHException
   Message: External component has thrown an exception.
   Source: mscorlib
   TargetSite: System.Reflection.AssemblyName nGetFileInformation(System.String)
   StackTrace: 
      at System.Reflection.AssemblyName.nGetFileInformation(String s)
      at System.Reflection.AssemblyName.GetAssemblyName(String assemblyFile)
      at SyncThread.Run() 
      at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
      at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
      at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
      at System.Threading.ThreadHelper.ThreadStart()

为什么下面的Catch块没有捕获到这个异常?

(也许这与此有关:这只发生在客户站点上,其中UNC路径指向的是不属于本地网络而是VPN上的远程服务器。)


在我们的情况下,我们正在使用Telerik RadGridView,在添加行时偶尔会遇到其中之一。我有同样的问题,为什么它不会被捕获。感谢Andreas的提问,以及@porges的答案。 - ebol2000
1个回答

17
自从.NET 4以来,一些SEHException表示进程状态已损坏,被称为“损坏的状态异常”。这些问题像是segfaults/access violations,当检测到内存损坏后,异常被抛出。

虽然这些错误仍然映射回托管的.NET SEHExceptions,但默认情况下无法捕获它们,所以try { ... } catch (Exception ex) { ... }不能处理它们。

您可以选择处理这些异常(通过属性或应用程序配置文件中的策略更改),但不建议这样做,因为您的程序现在可能正在处理无效数据:

CLR一直使用与程序本身引发的异常相同的机制将SEH异常传递给托管代码。只要代码不试图处理它无法合理处理的异常情况,这并不是一个问题。大多数程序在访问冲突后不能安全地继续执行。不幸的是,CLR的异常处理模型始终鼓励用户通过允许程序捕获System.Exception层次结构顶部的任何异常来捕获这些严重错误。但这很少是正确的做法。
编写catch (Exception e)是常见的编程错误,因为未处理的异常会带来严重后果。但您可能会认为,如果您不知道函数将引发哪些错误,那么在程序调用该函数时,应保护所有可能的错误。这似乎是一个合理的行动方案,直到您考虑在进程处于可能已损坏的状态时继续执行意味着什么。有时中止并重试是最好的选择:没有人喜欢看到Watson对话框,但重新启动程序比使数据损坏更好。
程序捕获来自其不了解的上下文的异常是一个严重的问题。但您无法通过使用异常规范或其他契约机制来解决该问题。而且,管理的程序能够接收SEH异常的通知非常重要,因为CLR是许多种应用程序和主机的平台。一些主机,例如SQL Server,需要完全控制其应用程序的进程。与本机代码交互的托管代码有时必须处理本机C ++异常或SEH异常。
但大多数编写catch (Exception e)的程序员实际上并不想捕获访问冲突。他们更希望在发生灾难性错误时停止程序的执行,而不是让程序在未知状态下运行。对于托管插件(如Visual Studio或Microsoft Office)的主机程序来说,这尤其如此。如果插件导致访问冲突,然后吞咽异常,则主机可能会在不知情的情况下损坏自身状态(或用户文件)。

这段引用来自(MSDN杂志中的)文章,其中有更详细的内容。

如果您决定处理这些异常,需要考虑很多事情——正如文章所述,“编写正确的代码以处理CSE并继续安全地运行进程非常困难。”

特别是,任何异常已经跳过的finally块都没有被执行(因此,例如任何文件句柄都会悬空,直到被收集),甚至受限执行区域也可能被跳过!


此外,您可能需要向微软报告此问题,因为 GetAssemblyName 不应该抛出这种类型的异常。

那么,System.Reflection.AssemblyName.GetAssemblyName如何会破坏内存? - Andreas
@Andreas:我在最后添加了一条注释,但你太快了!这似乎是一个错误,它不应该这样做。 - porges

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