如何从非致命的.NET异常中识别致命错误

时间:2013-01-07 18:19:34

标签: c# .net c#-4.0 exception fatal-error

我正在用C#编写一个通用的离散事件系统仿真库。我将在其上编写另一个库,它将实现特定类型的离散事件模拟。这是代码的完整版本。

static class Engine
{
    [ThreadStatic] internal static uint time;
    //...

    public static void Run(OnException onException = OnException.PauseAndRethrow,
                           IList<Type> exceptionsToPass = null)
    {
        //...
        while (!stop)
        {
            Event e = currentTimeEventQueue.PopEvent();
            if (e == null) break;
            try {
                e.Activate();
            }
            catch (EngineException ex)
            {
                // An engine method that shouldn't have been called
                // was called, and Activate didn't handle that

                // handle this situation...
            }
            catch (/* fatal exception type*/ ex)
            {
                throw;
            }
            catch (Exception ex)
            {
                // code to decides whether to dismiss exception
                // pause, stop, reset based on parameters to this method
            }
        }
    }
}

问题是:我是否应该专门捕获已知不可恢复的异常类型(我不应该尝试以任何方式处理)。那些例外是什么(我可以想到OutOfMemoryExceptionStackOverflowException)。是否有致命异常列表?我记得其中一些是无法捕捉的。 所以我对可以捕获的致命异常列表感兴趣。我只是想重新抛出它们,而不是做任何事情。另一方面,我想处理任何其他类型的异常。或者我可能需要另一个角度。


编辑:好的,我在写这个问题时做了很大的疏忽。 Activate()是抽象的。我正在编写一个通用的离散事件系统仿真库。引擎正在使用完全未知的Event子类。所以它调用一个绝对未知的方法Activate(),可能会抛出任何类型的异常。我可以忽略这个问题,但我想把这个过程控制给调用者。从参数到Run()方法可以看出,如果异常来自对Activate()的调用,调用者将决定引擎将执行的操作(它可以指示引擎忽略并继续或暂停和重新抛出,或...)。这就是我试图将致命与其他所有例外分开的原因。如果调用者指示引擎忽略来自Activate()的任何异常,则捕获并忽略致命异常是不明智的。 (有龙:))

3 个答案:

答案 0 :(得分:3)

  

我应该专门捕获已知不可恢复的异常类型

没有。你不应该。如果它们不可恢复,你不应该试图从中恢复。

关于例外的规则是 - 捕获并处理您知道如何从中恢复的异常。让任何其他泡沫升级 - 如果这意味着应用程序崩溃,这可能是最好的。

以下是代码气味,不应编码:

catch (/* fatal exception type*/ ex)
{
    throw;
}

答案 1 :(得分:2)

没有扔掉的东西真的“无法捕捉”; .NET中可以抛出的任何内容都源于Exception,因此可以捕获。但是,很多事情不应该被抓住。因此你的问题;如何区分?

我遵循的一般规则是“抓住您期望的例外并知道如何处理”。这需要您首先了解您的代码可以抛出的内容。 MSDN文档通常很擅长说明在什么情况下会抛出各种框架方法;除非您正在开发一个供其他编码人员使用的中间库(或者您的管理人员/潜在客户对于正确的文档是肛门的),否则您自己的代码库可能没有得到充分记录。

一旦你知道代码可以抛出什么,就要确定你应该抓住什么(如果有的话)。异常陷阱,又名“口袋妖怪处理”(Gotta catch'em all)通常是一件坏事,因为有合理的理由让你的应用程序死于火热的死亡并让用户重新启动它。示例包括StackOverflowExceptions,OutOfMemoryExceptions和各种Win32Exceptions,详细说明了为程序提供所请求资源的一些内部故障。你通常无法以任何有意义的方式从这些中恢复。

然而,大多数错误并不严重。当找不到文件,或拒绝或意外关闭网络连接时,或者当您尝试使用类型执行某些操作而不检查null时会抛出异常(当检查null失败时会发生什么?通常可以继续,必须抛出你自己的例外。这些是你应该期待,捕捉的东西,并且至少以一种可以理解的方式与最终用户沟通。

在许多情况下,try / throw / catch是预期但不是日常情况的有用工具;如果在等待结果时收到超时异常,通常在获得结果时没有问题,那么甚至不要告诉用户有问题;再试一次如果你可以(应该?)在无法计算计算时插入一些默认值(除以零,数学可能会产生非实际结果,但代码无法处理它们等等),那么就这样做。

然而,有一种情况我可以想到你必须捕获和重新抛出的地方,而这涉及数据库事务。如果您正在执行一个大型“工作单元”,并且数据库事务处理了一些持久性操作,那么如果任何出错,那么您应该回滚数据库事务。在这种情况下,操作应该包含在try-catch(异常)块中,该块捕获异常,回滚事务并重新抛出。您可以正常进行的任何异常都应该使用嵌套的try-catch来处理,或者应该在操作之前检查条件,这可能会以这种方式失败。

答案 2 :(得分:1)

你的问题是一个合理的问题,但唉,往往没有真正好的答案。 C ++中的异常范例的一个主要缺点是不可避免地进入其他语言和借用它的框架,它在异常对象的类型中封装太多。在C ++中,这是可以理解的,因为Bjarne Stroustrup希望避免将任何非原始类型“硬编码”到该语言中;鉴于这种约束,C ++设计可能是最好的。尽管如此,这种设计还是存在一些严重的局限性。

最大的问题是,正在考虑捕获和处理异常,然后吞咽或重新抛出异常的代码可能会对以下内容感兴趣:

  1. 阻止代码按预期运行的情况发生了什么
  2. 应采取什么措施来应对这种情况
  3. 一旦采取了各种行动,该条件是否应被视为“已解决”
  4. 如果一个人对系统状态的任何方面的假设超出方法失败的含义(例如,如果无法读取文件“foo / bar”),那么失败的性质可能会影响到决定是否尝试阅读“foo / boz”)。

在Java和.net中,期望的是应该使用异常类型来表示上面的#1,并且没有标准的异常方法来回答#2和#3,,即使它们在很多情况下也是如此比“发生了什么”更重要。如果一个人试图加载文档并出现问题,99%的时间系统将处于与加载尝试之前相同的状态,因此异常,无论其类型如何,基本上都意味着“文件不是可读,不加载“。此外,在剩余1%的时间内,在加载文件失败之后出现“坏”的情况,完全有可能的是异常类型与99%中的异常类型相同。

如果没有一些新的异常处理功能,最好的选择可能是,尽可能存在异常,指示状态损坏使系统状态的损坏部分无效,以便将来对这些部分的所有操作都会抛出异常。这种行为意味着代码无法处理的异常最终会导致系统崩溃(因为代码将继续尝试使用已损坏的状态部分),同时允许代码从应该恢复的内容中恢复(例如,如果由于正在构建的对象中的损坏而发生异常,并且如果异常导致代码放弃正在构建的对象,则这种放弃就足以“处理”异常,因此执行可以并且应该继续)。 / p>