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立即关闭引用的文件句柄。我可以在不使用usingtry/finally 块替换构造的情况下执行此操作吗?

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

我知道终结器最终会释放文件句柄,但我想知道是否有一种确定性的方法来解决这个问题,而不需要ThreadAbortException在每个CreateWriter使用的地方捕获。

4

4 回答 4

4

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

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

于 2011-06-06T18:03:04.330 回答
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
  }

我不确定这是可能的。

于 2011-06-06T18:16:35.570 回答
1

在许多情况下(但绝对不是全部),您可以防范ThreadAbortException. .NET BCL 中的大多数关键代码已经很好地做到了这一点。问题是很难做到正确。出于这个原因,大多数人建议并且正确地建议避免中止线程。从 2.0 版开始,CLR 使线程中止变得更加可容忍,并引入了一组新的 API 来帮助代码作者防范它们。查看受约束的执行区域,深入了解所有这些是如何工作的。

我相信您对块示例的担忧是正确的usingtry要使受约束的执行区域正常工作,必须在块内发生带外(异步)异常。但是,由于扩展的方式,表达式在块using之外进行评估。将其与从块内评估表达式try的块的扩展进行对比。好吧,无论如何,框架的 4.0 版都是如此,并且专门进行了更改以防止这些异常。locktry

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

所以是的。您的代码不能容忍带外(异步)异常。但是,那些比我聪明的人普遍认为它不应该是这样。

于 2011-06-06T18:37:27.583 回答
0

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

于 2011-06-06T18:16:02.467 回答