在ThreadAbortException的上下文中,防止资源泄漏的建议方法是什么?

时间:2011-06-06 17:57:08

标签: c# .net idisposable using threadabortexception

我正在努力改进一段代码的异常安全性,并且我意识到,即使用C#ThreadAbortException构造保护资源,引发的using也可能导致意外的资源泄漏。例如,请考虑以下代码(可以在单独的线程中运行)。

using (TextWriter writer = CreateWriter(filename))
{
    // do something with the writer.
}

TextWriter CreateWriter(string filename)
{
    return new CustomWriter(File.OpenWrite(filename));
}

如果运行此代码的线程异常终止,那么我希望立即关闭filename引用的文件句柄。我是否可以在不使用try / finally块替换using构造的用途的情况下执行此操作?

我的假设是ThreadAbortException可能随时被提出,这意味着我应该注意语句之间发生的事情。虽然我可以使用try / finally块来防止CreateWriter中的异常,但using构造在评估括号中的表达式之前将不会执行相同操作,这意味着文件资源保持打开状态如果在CreateWriter返回后立即发生异常。

我理解终结器最终将释放文件句柄,但我想知道是否有确定的方法来解决此问题而不会在ThreadAbortException使用的每个位置都捕获CreateWriter

4 个答案:

答案 0 :(得分:4)

是的,防止这种情况的确定性方法是不使用Thread.Abort。永远。向你的线程发出信号,这是时候停止,让他们优雅地终止。 Thread.Abort是一个伟大的大红鲱鱼,放在API中只是为了绊倒你。 ;)

http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation

答案 1 :(得分:1)

有一个权衡。

  1. 确保立即关闭所有资源,即使存在ThreadAbortException
  2. 代码更简单,但如果调用Abort(),则暂时泄漏资源
  3. 我认为你不是在打电话给Abort,而只是想要一个安全的方法,如果其他人这样做的话。如果你打电话给Abort,那我建议你不要。这不是您遇到的唯一问题。还有其他problems with Abort in the documentation

    #2是一个有效的选择,因为Abort()的调用者应该期待这一点。

    如果你想选择#1,那么我认为即使是简单的try / catch也会有所帮助。如果ThreadAbortException可以在任何地方发生,那么它仍然可以在文件打开后发生(在File.OpenWrite()内),然后你可以将它分配给你可以调用Dispose()的变量 - 你会遇到同样的问题在你的代码中使用。

    你需要像

    这样的语义
      using (var handle = GetUnOpenedHandle()) {
            handle.Open(); // this can't involve assignment to any field of handle
      }
    

    我不确定这是可能的。

答案 2 :(得分:1)

在许多情况下(但绝对不是全部),你可以防范ThreadAbortException。 .NET BCL中的大多数关键代码都已经做得相当好。问题是真的很难做对。因此,大多数人建议并且正确地避免中止线程。从版本2.0开始,CLR使线程中止更容易,并引入了一组新的API来帮助代码作者防范它们。请查看Constrained Execution Regions,深入了解所有这些是如何工作的。

我相信您对using块示例的关注是正确的。要使约束执行区域正常工作,带外(异步)异常必须在try块内发生。但是,由于using扩展的方式,表达式在try块之外进行计算。与lock块的扩展形成对比,该块在try块内评估表达式。好吧,无论如何,这是真的with version 4.0 of the framework,并且专门针对这些例外做了改变。

所以问题是为什么using块没有进行相同的更改。 According to Joe Duffy这是一个可以接受的遗漏,因为假设线程中止应该总是后面的AppDomain终止,无论如何都会触发终结器。

是的。您的代码不能容忍带外(异步)异常。但是,那些比我更聪明的人的普遍智慧是它不应该是。

答案 3 :(得分:0)

线程中止最常用于致命错误,因此您的响应应该是让应用程序终止。如果您尝试干净地停止自己的线程,请使用Thread.Join()。