views:

369

answers:

8

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?

+6  A: 

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.

Marc Gravell
+1 for the `null` explanation: you're right, not only constructors can be used in the `using` statement. Thanks!
DzinX
@DzinX - also, constructors can return null. And not *just* for `Nullable<T>` - for classes, too. No, really.
Marc Gravell
@Marc Gravell: that's interesting, how?
DzinX
See the second part ("the plot thickens") [here](http://stackoverflow.com/questions/194484/whats-the-strangest-corner-case-youve-seen-in-c-or-net/194671#194671)
Marc Gravell
+6  A: 

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.

Clyde
+2  A: 

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.

CodeInChaos
Thanks, this document is really useful.
DzinX
+3  A: 

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

CodeInChaos
Interesting. Do you have any reference that states it? I couldn't find it in the language specification.
DzinX
@dzinx @codeinchaos - see Eric lipperts blog. It is updated in a pending spec version (if not already). Also, events are overhauled too.
Marc Gravell
A: 

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.

smirkingman
+2  A: 

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.

Hans Passant
A: 

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

Tokk
How does that help? If the abort happens before the assignment of "reader" (but after the file is opened) it will be null and the finally statement does nothing.
adrianm
You're missing a subtlety there. The doc says that the finally runs regardless of how the try block is *exited*, which *presumes that it was in fact exited*. Control *never* leaves the try block in some situations, and therefore the finally block never runs in those situations. For example, if the protected region fails fast.
Eric Lippert
@Eric In my Oppinion (and some quik test argree with it) as soon as only one line in a try block is executet you can be sure that the finally block is used, no matter what happens.
Tokk
That's certainly not true. Besides rude aborts, consider the much more obvious case where your try body consists of something like `while (true) { }`.
kvb
@Tokk - `try { while(true){} } finally { Console.WriteLine("Will this ever be printed?"); }`
Jeffrey L Whitledge
ok, you are right...is the Explanation of MSDN false? I mean you exit try with exiting app, or not?
Tokk
@Tokk: I tell you here's the deal: I will always give you a boat after you give me a million dollars. You have a million dollars. Does this logically imply that at some point in your life you will get a boat? No, of course not. You could live forever and never give me the million dollars. Or you could die still holding the million dollars. Either way, you never get a boat. The finally block only runs when control leaves the try. If control never leaves the try, or the process is completely destroyed while control is in the try, then how can the finally run?
Eric Lippert
@Eric I thought you would steel my money when I'm dead and finally keep your boat :-D
Tokk
+7  A: 

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

Joe Albahari
Great article, this is exactly what I was looking for. And from the author himself! Thank you!
DzinX
+1 for getting an answer from the author
statichippo