Is C#'s using statement abort-safe?

纵饮孤独 提交于 2019-12-28 05:49:31

问题


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?


回答1:


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).




回答2:


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.




回答3:


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.




回答4:


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




回答5:


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.




回答6:


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.




回答7:


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.




回答8:


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.




回答9:


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 ;-))



来源:https://stackoverflow.com/questions/3923457/is-cs-using-statement-abort-safe

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!