在 ThreadAbortException 的情况下,有什么推荐的方法来防止资源泄漏?

4

我正在改进一段代码的异常安全性,并意识到引发ThreadAbortException可能会导致不希望的资源泄漏,即使使用C#的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 ,这意味着我应该注意语句之间发生的事情。虽然我可以在 CreateWriter 中使用try / finally块来防范异常,但 using 构造在括号中的表达式被评估之后才会执行相同操作,这意味着如果异常在 CreateWriter 返回之后立即发生,则文件资源将保持打开状态。
我知道最终器最终会释放文件句柄,但我想知道是否有一种确定性的方法来解决此问题,而无需在每个使用 CreateWriter 的地方捕获 ThreadAbortException
4个回答

4
是的,防止这种情况发生的确定性方式是永远不要使用Thread.Abort。向您的线程发出信号表示是停止的时候了,并让它们优雅地终止。在API中放置Thread.Abort仅是为了使您困惑。 ;)
参考链接:http://www.interact-sw.co.uk/iangblog/2004/11/12/cancellation

公平地说,MSDN也警告不要使用它。它仅用于向后兼容性维护。 - H H

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

我认为你对于using块的例子有正确的担忧。为了让受限执行区域正常工作,异步异常必须在try块内发生。但是,由于using展开表达式的方式,表达式的求值在try块之外。与此相比,lock块的展开会在try块内部求值。无论如何,这在框架版本4.0中是正确的,并且这是专门为了防范这些异常而进行的更改。

那么问题是为什么没有用 using 块做同样的改变。根据Joe Duffy的说法,这是可以接受的遗漏,因为假设线程异常终止始终会被应用程序域终止,从而触发最终器。

所以,是的。你的代码对于异步异常不容忍。但是,比我聪明的人的普遍智慧是它不应该这样做。


1

这里有一个权衡。

  1. 确保立即关闭所有资源,即使存在ThreadAbortException
  2. 拥有更简单的代码,但如果调用Abort()则会暂时泄露资源

我假设您没有调用Abort,并且只是想找到一种安全的方法以防别人这样做。如果您正在调用Abort,则建议您不要这样做。这不是您将遇到的唯一问题。文档中还有其他关于Abort的问题

#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
  }

我不确定这是可能的。


0

线程中止通常用于致命错误的情况,因此您的响应应该是让应用程序终止。如果您尝试清除自己的线程,请使用Thread.Join()。


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