8

I have some critical logic in a finally block (with an empty try block), because I want to guarantee that the code gets executed even if the thread is aborted. However, I'd also like to detect the ThreadAbortException. I've found that wrapping my critical try/finally block in a try/catch does not catch the ThreadAbortException. Is there any way to detect it?

try {
    try { }
    finally {
        // critical logic
    }
} catch(Exception ex) {
    // ThreadAbortException is not caught here, but exceptions thrown
    // from within the critical logic are
}
Kiquenet
  • 14,494
  • 35
  • 148
  • 243
Chris
  • 3,664
  • 6
  • 34
  • 44

7 Answers7

8

This is a curious problem.

The code you posted should work. It seems there's some kind of optimization going on that decides not to call your catch handler.

So, I wanted to detect the exception with this:

bool threadAborted = true;
try {
  try { }
  finally { /* critical code */ }
  threadAborted = false;
}
finally {
  Console.WriteLine("Thread aborted? {0}", threadAborted);
}
Console.WriteLine("Done");

(My actual code just slept in that critical code section, so I could be sure it would abort after that finally.)

It printed:

Thread aborted? False

Hmmm, strange indeed!

So I thought about doing a little bit more work there, to trick any "smart" optimizations:

bool threadAborted = true;
try {
  try { }
  finally { /* critical code */ }
  threadAborted = AmIEvil();
}
finally {
  Console.WriteLine("Thread aborted? {0}", threadAborted);
}
Console.WriteLine("Done");

Where AmIEvil is just:

[MethodImpl(MethodImplOptions.NoInlining)]
static bool AmIEvil() {
  return false;
}

Finally it printed:

Thread aborted? True

And there you have it. Use this in your code:

try {
  try { }
  finally { /* critical code */ }
  NoOp();
}
catch (Exception ex) {
  // ThreadAbortException is caught here now!
}

Where NoOp is just:

[MethodImpl(MethodImplOptions.NoInlining)]
static void NoOp() { }
Jordão
  • 55,340
  • 13
  • 112
  • 144
  • The finally is not being skipped. The `ThreadAbortException` was waiting to be thrown until _after_ the `catch` because the `try` was in a CER (Constrained Execution Region). The method call makes it such the it cannot be a CER. – Nick Whaley Jun 06 '13 at 15:18
3

You can actually execute code in the catch statement just fine for a ThreadAbortException. The problem is that the exception will be rethrown once execution leaves the catch block.

If you want to actually stop the exception from continuing you can call Thread.ResetAbort(). This does require full trust though and unless you have a specific scenario, it's almost certainly the wrong thing to do.

ThreadAbortException

JaredPar
  • 733,204
  • 149
  • 1,241
  • 1,454
  • Right, but I want the code to execute regardless of whether the TAE is thrown or not, so I can't just put this logic in the TAE handler, nor would it be clean to implement it twice. Basically, I want to avoid having to write "back-out" logic in my catch or finally blocks. – Chris Dec 09 '08 at 16:09
  • I'm not sure I understand, both the catch and finally will run with a TAE exception. It will just be rethrown at the end of a catch – JaredPar Dec 09 '08 at 16:24
2

Read about Constrained Execution Regions.

Specifically, the RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup method will be useful here.

Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • I did, and the only benefit it provides beyond what I'm currently doing is that it prepares all of the resources before executing the finally block. – Chris Dec 09 '08 at 16:06
  • Actually, you CERs are not just for ThreadAbortException. I don't think it's a good idea to write any application specific code that relies on occurrence of ThreadAbortException. And basically, finally blocks are there to cleanup resources, which CERs help guarantee execution. – Mehrdad Afshari Dec 09 '08 at 16:15
2

I don't think that it's possible.

Why do you need to handle the ThreadAbortException in the first place? Calling thread.Abort() is usually a sign of bad design. Have a flag variable that when set to true will simply return; from the thread function, after appropriate cleanup of course.

That way you won't need to worry about the exception.

arul
  • 13,998
  • 1
  • 57
  • 77
  • You make a good point about Thread.Abort(), but redesigning that aspect of our app might be too risky at this point. – Chris Dec 09 '08 at 16:38
2

If calling Thread.Abort is bad design why does SQL Server call it on a thread that is running user code? Because this is exactly how a query cancel is handled and it causes nightmares.

Hugh
  • 21
  • 1
0

I agree with arul. Calling Thread.Abort() is a sign of bad design.

Let me quote Peter Ritchie from MSDN: Thread.Abort (emphasis is mine):

There's many reasons not to use Thread.Abort and ThreadAbortException

On certain platforms (like x64 and IA64) the abort can occur before Monitor.Enter and a try block (even with lock/SyncLock), leaving the monitor orphaned. The ThreadAbortException can occur in 3rd party code not written to handle thread abort. The thread can be aborted while processing a finally block in .NET 1.x Uses exceptions for normal control flow logic. Asynchronous exception can interrupt modification of shard state or resources, leaving them corrupted.

For more detail see:
http://msmvps.com/blogs/peterritchie/archive/2007/08/22/thead-abort-is-a-sign-of-a-poorly-designed-program.aspx
http://www.bluebytesoftware.com/blog/2007/01/30/MonitorEnterThreadAbortsAndOrphanedLocks.aspx
http://blogs.msdn.com/ericlippert/archive/2007/08/17/subtleties-of-c-il-codegen.aspx

DonkeyMaster
  • 1,302
  • 4
  • 17
  • 36
0

Have you tried something like this?

try {
    try { }
    catch (ThreadAbortException)
    {
      ThreadAbortExceptionBool = true;
    }
    finally {
        // critical logic
        if (ThreadAbortExceptionBool)
          // Whatever
    }
} 
catch(Exception ex) {
    // ThreadAbortException is not caught here, but exceptions thrown
    // from within the critical logic are
}
Jorge Córdoba
  • 51,063
  • 11
  • 80
  • 130