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

我刚刚读完了“C#4.0 in a Nutshell”(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等同于第一个变体(不是中止安全),为什么finally检查null
  3. 根据该书(p.856),可以在托管代码中的任何位置抛出ThreadAbortException 。 但也许有例外,第一个变种毕竟是中止安全的?

编辑:我知道使用thread.Abort()不被认为是好习惯。 我的兴趣纯粹是理论上的: using陈述的行为如何?

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

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

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

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

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

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

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

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

Thread.Abort是非常非常糟糕的juju; 如果有人打电话说你已经遇到了很多麻烦(不可恢复的锁等)。 Thread.Abort应该被限制在一个病态过程中的scanerio。

exception通常是干净地展开,但在极端情况下,并不能保证每一段代码都能执行。 一个更紧迫的例子是“如果电源出现故障会怎么样?”。

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

有点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

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

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时才会中止线程。

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

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

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

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

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

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

Thread.Abort只会等待在Constrained Execution Regions(CERs)内运行的代码, finally块, catch块,静态构造函数和非托管代码。 所以这是一个中止安全的解决方案( 涉及资源的获取和处置):

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

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

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

检查null使得using模式在存在null引用时是安全的。

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

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

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

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

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

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