代码之家  ›  专栏  ›  技术社区  ›  Weeble

我应该如何处理Dispose()方法中的异常?

  •  15
  • Weeble  · 技术社区  · 14 年前

    我想提供一个类来管理临时目录的创建和随后的删除。理想情况下,我希望它可以在using块中使用,以确保目录再次被删除,而不管我们如何离开该块:

    static void DoSomethingThatNeedsATemporaryDirectory()
    {
        using (var tempDir = new TemporaryDirectory())
        {
            // Use the directory here...
            File.WriteAllText(Path.Combine(tempDir.Path, "example.txt"), "foo\nbar\nbaz\n");
            // ...
            if (SomeCondition)
            {
                return;
            }
            if (SomethingIsWrong)
            {
                throw new Exception("This is an example of something going wrong.");
            }
        }
        // Regardless of whether we leave the using block via the return,
        // by throwing and exception or just normally dropping out the end,
        // the directory gets deleted by TemporaryDirectory.Dispose.
    }
    

    创建目录没问题。问题是如何编写Dispose方法。当我们试图删除目录时,可能会失败;例如,因为我们在其中仍然有一个打开的文件。但是,如果我们允许传播异常,它可能会屏蔽在using块中发生的异常。特别是,如果在using块内发生异常,则可能是 引起 我们无法删除目录,但是如果我们屏蔽它,我们就丢失了修复问题的最有用信息。

    我们似乎有四种选择:

    1. 尝试删除目录时捕获并咽下任何异常。我们可能不知道我们无法清理临时目录。
    2. 不知何故,在引发异常时检测Dispose是否作为堆栈展开的一部分运行,如果是这样,则抑制IOException或引发合并IOException和引发其他异常的异常。可能根本不可能。(我之所以想到这一点,部分是因为使用Python的 context managers ,在许多方面与.NET的IDisposable与C的using语句类似。)
    3. 从不禁止IOException删除目录失败。如果在using块中抛出了异常,我们将隐藏它,尽管很可能它比ioexception具有更多的诊断价值。
    4. 放弃删除Dispose方法中的目录。类的用户必须继续负责请求删除目录。这似乎并不令人满意,因为创建班级的动机很大程度上是为了减轻管理这种资源的负担。也许还有另一种方法来提供这个功能,而不让它很容易出错?

    其中一个选择显然是最好的吗?在用户友好的API中是否有更好的方法来提供此功能?

    7 回复  |  直到 14 年前
        1
  •  7
  •   Aaronaught    14 年前

    而不是把它当作一个特殊的类来实现 IDisposable ,考虑正常程序流的情况:

    Directory dir = Directory.CreateDirectory(path);
    try
    {
        string fileName = Path.Combine(path, "data.txt");
        File.WriteAllText(fileName, myData);
        UploadFile(fileName);
        File.Delete(fileName);
    }
    finally
    {
        Directory.Delete(dir);
    }
    

    这应该怎么办?这是完全相同的问题。你留下的内容 finally 按原样阻塞,从而可能掩盖发生在 try 阻止,还是包装 Directory.Delete 它自己 try-catch 阻止,吞咽任何例外,以防止掩盖原来?

    我觉得没有 正确答案 -事实上,您只能有一个环境异常,因此必须选择一个。然而,.NET框架确实设置了一些先例;一个例子是WCF服务代理( ICommunicationObject )如果你试图 Dispose 一个通道出现故障,它抛出异常并 屏蔽堆栈上已存在的任何异常。如果我没弄错, TransactionScope 也可以这样做。

    当然,WCF中的这种行为是一种无止境的困惑;大多数人实际上认为,如果不打破它,它会非常恼人。谷歌“wcf dispose mask”,你就会明白我的意思了。所以也许我们不应该总是像微软那样做。

    我个人认为 处置 不应屏蔽堆栈上已存在的异常。这个 using 声明实际上是 最后 块和 大部分时间 (总是有边缘情况),您不希望在 最后 也可以阻止。原因只是调试;它可以 极其 很难找到问题的根源——尤其是在生产中,你无法从源头入手的问题——当你甚至没有能力找出应用到底在哪里失败的时候。我以前就在这个职位上,我可以自信地说,这会让你完全疯狂。

    我的建议是要么在 处置 (当然要记录下来),或者实际检查一下 already in a stack-unwinding scenario due to an exception 只有当你知道你会掩盖它们的时候,才会吃到随后的例外。后者的优点是,除非确实需要,否则您不会吃异常;缺点是您已经在程序中引入了一些不确定性行为。另一个权衡。

    大多数人可能会选择前一种方法,只是隐藏在 最后 (或) 使用 )

        2
  •  2
  •   Randolpho    14 年前

    最后,我建议最好遵循 FileStream 作为准则,这相当于选项3和4:关闭文件或删除 Dispose 方法,并允许作为该操作的一部分发生的任何异常冒泡(有效地吞咽在 using 块),但允许手动关闭资源而不需要使用块(如果组件的用户如此选择)。

    与msdn的文档不同 文件流 ,我建议您详细记录如果用户选择使用 使用 声明。

        3
  •  0
  •   MZB    14 年前

    这里要问的一个问题是,异常是否可以由调用者有效地处理。如果没有任何合理的用途(手动删除目录中使用的文件?)最好记录错误并忘记它。

    为了涵盖这两种情况,为什么不使用两个构造函数(或构造函数的参数)?

    public TemporaryDirectory()
    : this( false )
    {
    }
    
    public TemporaryDirectory( bool throwExceptionOnError )
    {
    }
    

    然后,您可以向类的用户推送关于适当行为可能是什么的决定。

    一个常见的错误是无法删除的目录,因为其中的一个文件仍在使用中:您可以存储一个未删除的临时目录列表,并允许在程序关闭期间再次显式尝试删除(例如temporary directory.tidyup()静态方法)。如果有问题的目录列表为非空,则代码可能会强制垃圾收集处理未关闭的流。

        4
  •  0
  •   Vlad    14 年前

    您不能依赖这样的假设,即您可以以某种方式删除目录。其他进程/用户/任何可以同时在其中创建文件的对象。防病毒软件可能正忙于检查其中的文件等。

    你能做的最好的事情是不仅有临时目录类,还有临时文件类(在内部创建 using 阻止临时目录。临时文件类应(尝试)删除上的相应文件 Dispose . 这样,您就可以保证至少已经完成了一次清理尝试。

        5
  •  0
  •   David    14 年前

    我会说,从一个锁定文件的析构函数中抛出一个异常,归根结底就是使用一个异常来报告一个预期的结果——你不应该这样做。

    但是,如果发生了其他情况,例如变量为空,那么您可能确实有错误,那么异常是有价值的。

    如果您预期锁定的文件,并且您或潜在的调用者可以对它们做些什么,那么您需要在类中包含该响应。如果你能回应,那就在一次性电话里做吧。如果您的调用者可能能够响应,请向您的调用者提供一种方法,例如tempfileslocked事件。

        6
  •  0
  •   João Angelo    14 年前

    假设创建的目录位于系统临时文件夹中,就像 Path.GetTempPath 然后我会执行 Dispose 以便在删除临时目录失败时不引发异常。

    更新: 我会根据这样一个事实来选择这个选项:操作可能会因为关闭外部干扰而失败,比如来自另一个进程的锁,而且由于目录被放置在系统临时目录中,所以我看不到引发异常的好处。

    对于那个例外,什么是有效的响应?再次尝试删除目录是不合理的,如果原因是来自另一个进程的锁,说明它不在您的直接控制之下。

        7
  •  -1
  •   Joel Coehoorn    14 年前

    在a中使用类型 using 语句,您要实现 IDisposable 模式。

    要创建目录本身,请使用 Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) 作为基础,新的guid作为名称。

    推荐文章