C# 的 using 语句中止安全吗?

发布于 2024-09-27 10:20:45 字数 1208 浏览 3 评论 0原文

我刚刚读完《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. 根据该书(第 856 页),ThreadAbortException 可以在托管代码中的任何位置抛出。但也许有例外,第一个变体毕竟是中止安全的?

编辑:我知道使用thread.Abort()不被认为是好的做法。我的兴趣纯粹是理论上的:using 语句的行为如何准确

I've just finished reading "C# 4.0 in a Nutshell" (O'Reilly) and I think it's a great book for a programmer willing to switch to C#, but it left me wondering. My problem is the definition of using statement. According to the book (p. 138),

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

is precisely equivalent to:

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

Suppose, however, that this is true and that this code is executed in a separate thread. This thread is now aborted with thread.Abort(), so a ThreadAbortException is thrown and suppose the thread is exactly after initializing the reader and before entering the try..finally clause. This would mean that the reader is not disposed!

A possible solution would be to code this way:

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

This would be abort-safe.

Now for my questions:

  1. Are authors of the book right and the using statement is not abort-safe or are they wrong and it behaves like in my second solution?
  2. If using is equivalent to the first variant (not abort-safe), why does it check for null in finally?
  3. According to the book (p. 856), ThreadAbortException can be thrown anywhere in managed code. But maybe there are exceptions and the first variant is abort-safe after all?

EDIT: I know that using thread.Abort() is not considered good practice. My interest is purely theoretical: how does the using statement behave exactly?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

苦行僧 2024-10-04 10:20:45

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

总之,第一个翻译是正确的(看IL就知道了)。

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

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

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

The book's companion web site has more info on aborting threads here.

In short, the first translation is correct (you can tell by looking at the IL).

The answer to your second question is that there may be scenarios where the variable can be legitimately null. For instance, GetFoo() may return null here, in which you wouldn't want a NullReferenceException thrown in the implicit finally block:

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

To answer your third question, the only way to make Abort safe (if you're calling Framework code) is to tear down the AppDomain afterward. This is actually a practical solution in many cases (it's exactly what LINQPad does whenever you cancel a running query).

雄赳赳气昂昂 2024-10-04 10:20:45

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

基本上,当您收到 ThreadAbortException 时,所有的赌注都消失了。这就是为什么您永远不应该故意中止线程,而不是使用其他一些方法来优雅地关闭线程。

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

There's really no difference between your two scenarios -- in the second, the ThreadAbort could still happen after the call to OpenText, but before the result is assigned to the reader.

Basically, all bets are off when you get a ThreadAbortException. That's why you should never purposely abort threads rather than using some other method of gracefully bringing the thread to a close.

In response to your edit -- I would point out again that your two scenarios are actually identical. The 'reader' variable will be null unless the File.OpenText call successfully completes and returns a value, so there's no difference between writing the code out the first way vs. the second.

半寸时光 2024-10-04 10:20:45

Thread.Abort 是非常非常糟糕的juju;如果人们打电话说您已经遇到了很多麻烦(无法恢复的锁等)。 Thread.Abort 实际上应该仅限于破坏病态进程的扫描。

异常通常会被干净地展开,但在极端情况下,不能保证每一位代码都能执行。一个更紧迫的例子是“如果停电会发生什么?”。

重新检查null;如果 File.OpenText 返回 null 会怎样?好吧,它不会,但是编译器不知道这一点。

Thread.Abort is very very bad juju; if people are calling that you're already in a lot of trouble (unrecoverable locks, etc). Thread.Abort should really be limited to the scanerio of inhuming a sickly process.

Exceptions are generally unrolled cleanly, but in extreme cases there is no guarantee that every bit of code can execute. A more pressing example is "what happens if the power fails?".

Re the null check; what if File.OpenText returned null? OK, it won't but the compiler doesn't know that.

仙女山的月亮 2024-10-04 10:20:45

有点离题,但线程中止期间锁定语句的行为也很有趣。 while lock 相当于:(

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

A bit offtopic but the behaviour of the lock statement during thread abortion is interesting too. While lock is equivalent to:

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

It is guaranteed(by the x86 JITter) that the thread abort doesn't occur between Monitor.Enter and the try statement.
http://blogs.msdn.com/b/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspx

The generated IL code seems to be different in .net 4:
http://blogs.msdn.com/b/ericlippert/archive/2009/03/06/locks-and-exceptions-do-not-mix.aspx

宫墨修音 2024-10-04 10:20:45

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

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 规范

如果线程中止,两个代码变体都可能失败。第二个是如果中止发生在表达式求值之后但在对局部变量进行赋值之前。

但无论如何您都不应该使用线程中止,因为它很容易破坏应用程序域的状态。因此,只有在强制卸载应用程序域时才会中止线程。

The language spec clearly states that the first one is correct.

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

In case of a thread aborting both code variants can fail. The second one if the abort occurs after the expression has been evaluated but before the assignment to the local variable occurred.

But you shouldn't use thread abortion anyways since it can easily corrupt the state of the appdomain. So only abort threads if you force unload an appdomain.

辞取 2024-10-04 10:20:45

你关注的是错误的问题。 ThreadAbortException 很可能会中止 OpenText() 方法。您可能希望它能够适应这种情况,但事实并非如此。框架方法没有尝试处理线程中止的 try/catch 子句。

请注意,该文件不会永远保持打开状态。 FileStream 终结器最终将关闭文件句柄。当然,当您继续运行并尝试在终结器运行之前再次打开文件时,这仍然可能会导致程序出现异常。尽管当您在多任务操作系统上运行时,您始终必须对此保持警惕。

You are focusing on the wrong problem. The ThreadAbortException is just as likely to abort the OpenText() method. You might hope that it is resilient to that but it isn't. The framework methods do not have try/catch clauses that try to deal with a thread abort.

Do note that the file doesn't remain opened forever. The FileStream finalizer will, eventually, close the file handle. This of course can still cause exceptions in your program when you keep running and try to open the file again before the finalizer runs. Albeit that this is something you always have to be defensive about when you run on a multi-tasking operating system.

听不够的曲调 2024-10-04 10:20:45

这本书的作者是正确的,并且 using 语句不是中止安全的,还是他们错了,它的行为就像我的第二个解决方案一样?

根据本书(第 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();
}

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

如果 using 等同于第一个变体(不是中止安全的),为什么它要在 finally 中检查 null ?

检查 null 可以使 using 模式在存在 null 引用时安全。

Are authors of the book right and the using statement is not abort-safe or are they wrong and it behaves like in my second solution?

According to the book (p. 856), ThreadAbortException can be thrown anywhere in managed code. But maybe there are exceptions and the first variant is abort-safe after all?

The authors are right. The using block is not abort-safe. Your second solution is also not abort-safe, the thread could be aborted in the middle of the resource acquisition.

Although it's not abort-safe, any disposable that has unmanged resources should also implement a finalizer, which will eventually run and clean up the resource. The finalizer should be robust enough to also take care of not completely initialized objects, in case the thread aborts in the middle of the resource acquisition.

A Thread.Abort will only wait for code running inside Constrained Execution Regions (CERs), finally blocks, catch blocks, static constructors, and unmanaged code. So this is an abort-safe solution (only regarding the acquisition and disposal of the resource):

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

But be careful, abort-safe code should run fast and not block. It could hang a whole app domain unload operation.

If using is equivalent to the first variant (not abort-safe), why does it check for null in finally?

Checking for null makes the using pattern safe in the presence of null references.

嗫嚅 2024-10-04 10:20:45

前者确实与后者完全等价。

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

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

也就是说,一旦您进入 ThreadAbort,为什么还要费力去尝试清理呢?无论如何,你正处于死亡的痛苦之中。

The former is indeed exactly equivalent to the latter.

As already pointed out, ThreadAbort is indeed a bad thing, but it's not quite the same as killing the task with Task Manager or switching off your PC.

ThreadAbort is an managed exception, which the runtime will raise when it is possible, and only then.

That said, once you're into ThreadAbort, why bother trying to cleanup? You're in death throes anyway.

吃素的狼 2024-10-04 10:20:45

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

所以你不必担心不清理资源等(只有当Windows,框架运行时或其他你无法控制的坏事发生时,但是还有比清理资源更大的问题;-))

the finally-statement is always executed, MSDN says "finally is used to guarantee a statement block of code executes regardless of how the preceding try block is exited."

So you don't have to worry about not cleaning resources etc (only if windows, the Framework-Runtime or anything else bad you can't control happens, but then there are bigger problems than cleaning up Resources ;-))

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文