C#的使用语句是否中止?

时间:2010-10-13 12:14:48

标签: c# multithreading thread-safety using-statement abort

我刚读完“Nutshell中的C#4.0”(O'Reilly),我认为这对于愿意转换到C#的程序员来说是一本好书,但它让我感到疑惑。我的问题是using陈述的定义。根据这本书(第138页),

using (StreamReader reader = File.OpenText("file.txt")) {
    ...
}

恰好相当于:

StreamReader reader = File.OpenText("file.txt");
try {
    ...
} finally {
    if (reader != null)
        ((IDisposable)reader).Dispose();
}

但是,假设这是真的,并且此代码在单独的线程中执行。此线程现在已使用thread.Abort()中止,因此抛出ThreadAbortException并假设线程正好在初始化读取器之后和输入try..finally子句之前。这意味着读者不会被处置!

一种可能的解决方案是以这种方式编码:

StreamReader reader = null;
try {
    reader = File.OpenText("file.txt");
    ...
} finally {
    if (reader != null)
        ((IDisposable)reader).Dispose();
}

这将是中止安全的。

现在提出我的问题:

  1. 这本书的作者是否正确且using陈述是不是中止安全还是错误,它的行为与我的第二个解决方案相似?
  2. 如果using等同于第一个变体(不是中止安全),为什么要检查null中的finally
  3. 根据该书(第856页),ThreadAbortException可以在托管代码中的任何位置抛出。但也许有例外,第一个变种毕竟是中止安全的吗?
  4. 编辑:我知道使用thread.Abort()并不是一种好习惯。我的兴趣纯粹是理论上的:using陈述如何表现完全

9 个答案:

答案 0 :(得分:17)

本书的配套网站提供了有关中止线程here的更多信息。

简而言之,第一次翻译是正确的(你可以通过查看IL来判断)。

您的第二个问题的答案是,可能存在变量可以合法地为空的情况。例如,GetFoo()可能在这里返回null,在这里你不希望在隐式finally块中抛出NullReferenceException:

using (var x = GetFoo())
{
   ...
}

要回答您的第三个问题,使Abort安全的唯一方法(如果您正在调用Framework代码)是之后拆除AppDomain。在许多情况下,这实际上是一个实用的解决方案(正如LINQPad取消正在运行的查询时所做的那样)。

答案 1 :(得分:8)

两种情况之间确实没有区别 - 第二种情况,在调用OpenText之后,但在将结果分配给阅读器之前,ThreadAbort仍然可能发生。

基本上,当你收到ThreadAbortException时,所有的赌注都会被关闭。这就是为什么你不应该故意中止线程而不是使用其他方法优雅地使线程结束。

为了回应你的编辑 - 我会再次指出你的两个场景实际上是相同的。除非File.OpenText调用成功完成并返回一个值,否则'reader'变量将为null,因此在第一种方式和第二种方式之间编写代码没有区别。

答案 2 :(得分:6)

Thread.Abort是非常糟糕的juju;如果有人打电话说你已经处于很多的麻烦(不可恢复的锁等)。 Thread.Abort应该真正限于开始病态过程的scanerio。

异常通常干净地展开,但在极端情况下,无法保证每一位代码都可以执行。一个更紧迫的例子是“如果电源出现故障会发生什么?”。

重新null检查;如果File.OpenText返回null怎么办?好的,不会,但编译器不知道。

答案 3 :(得分:4)

有点offtopic但线程堕胎期间锁定语句的行为也很有趣。虽然锁相当于:

object obj = x;
System.Threading.Monitor.Enter(obj);
try {
    …
}
finally {
    System.Threading.Monitor.Exit(obj);
}

保证(通过x86 JITter)在Monitor.Enter和try语句之间不会发生线程中止。
http://blogs.msdn.com/b/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspx

生成的IL代码在.net 4中似乎有所不同:
http://blogs.msdn.com/b/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx

答案 4 :(得分:2)

语言规范明确指出第一个是正确的。

http://msdn.microsoft.com/en-us/vcsharp/aa336809.aspx MS Spec(Word文档)
http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-334.pdf ECMA规范

如果线程中止,则两个代码变体都可能失败。第二个是在表达式被评估之后但在分配给局部变量之前发生的中止。

但是你不应该使用线程堕胎,因为它很容易破坏appdomain的状态。因此,如果您强制卸载appdomain,则只能中止线程。

答案 5 :(得分:2)

你正专注于错误的问题。 ThreadAbortException也可能会中止OpenText()方法。你可能希望它具有弹性,但事实并非如此。框架方法有尝试处理线程中止的try / catch子句。

请注意,该文件不会永久保持打开状态。 FileStream终结器最终会关闭文件句柄。当然,当你继续运行并且在终结器运行之前再次尝试打开文件时,这仍然会导致程序中出现异常。虽然这是你在多任务操作系统上运行时总是需要防守的。

答案 6 :(得分:2)

  

本书的作者是否正确且使用声明是不是中止安全还是错误,它的行为与我的第二个解决方案相似?

     

根据该书(p.856),可以在托管代码中的任何位置抛出ThreadAbortException。但也许有例外,第一个变种毕竟是中止安全的吗?

作者是对的。 using块不是中止安全的。您的第二个解决方案也不是中止安全的,线程可以在资源获取过程中中止。

虽然它不是中止安全的,但任何具有无人资源的一次性用户也应该实现终结器,这将最终运行并清理资源。终结器应该足够健壮,以便在线程在资源获取过程中中止的情况下处理未完全初始化的对象。

Thread.Abort只会等待在约束执行区(CER),finally块,catch块,静态构造函数和非托管代码中运行的代码。所以这是一个中止安全的解决方案(关于资源的获取和处置):

StreamReader reader = null;
try {
  try { }
  finally { reader = File.OpenText("file.txt"); }
  // ...
}
finally {
  if (reader != null) reader.Dispose();
}

小心,中止安全的代码应该快速不阻止。它可以挂起整个应用程序域卸载操作。

  

如果使用等同于第一个变体(不是中止安全),为什么最终会检查null?

检查null使using模式在null个引用的情况下安全。

答案 7 :(得分:0)

前者确实完全等同于后者。

正如已经指出的那样,ThreadAbort确实是一件坏事,但它与使用任务管理器终止任务或关闭你的PC并不完全相同。

ThreadAbort是一个托管异常,运行时会在可能的情况下引发异常,并且只有这样。

那就是说,一旦你进入ThreadAbort,为什么还要费心去清理呢?无论如何,你正处于死亡之中。

答案 8 :(得分:-2)

始终执行finally语句,MSDN表示“最终用于保证语句代码块的执行,而不管前面的try块是如何退出的。”

所以你不必担心没有清理资源等(只有当windows,Framework-Runtime或其他任何你无法控制的错误发生时,但是然后存在比清理资源更大的问题;-))