4

UPDATE: I don't think this question is a duplicate of Can ThreadAbortException skip finally? because (1) I'm not creating another thread, so there's no possibility of a race condition, and (2) this behavior only occurs if the finally block contains an await, which that other question doesn't mention.


Consider this console program:

class Program
{
    static void Main()
    {
        try { T().GetAwaiter().GetResult(); }
        catch (ThreadAbortException) { Thread.ResetAbort(); }
        catch { }
    }

    static async Task Abort()
    {
        //await Task.Delay(1); // A
        Thread.CurrentThread.Abort(); // B
    }

    static async Task T()
    {
        try
        {
            await Abort();
        }
        catch
        {
            Console.WriteLine("catch");
            throw;
        }
        finally
        {
            Console.WriteLine("finally");
            await Task.Yield(); // C
        }
    }
}

When I compile this in Visual Studio 2015, the output is

catch

But if I make any one of these changes...

  1. Uncomment line A (and delete the call to Thread.ResetAbort() in Main—another oddity)
  2. Change line B to throw new Exception();
  3. Delete line C

then the output is

catch
finally

Is this behavior a bug, or is it by design (and documented somewhere)?

NOTE: In my actual scenario (an ASP.NET app), the ThreadAbortException is thrown by HttpResponse.Redirect, and I'm performing async I/O in the finally block.

Michael Liu
  • 52,147
  • 13
  • 117
  • 150
  • 3
    Possible duplicate of [Can ThreadAbortException skip finally?](https://stackoverflow.com/questions/18002668/can-threadabortexception-skip-finally) – Jonathon Chase Jan 11 '18 at 20:28
  • Do **not** use `Thread.Abort`. It **will** destabilize your program, and leave it in a completely unpredictable state. – Bradley Uffner Jan 11 '18 at 20:47
  • 1
    @BradleyUffner: Thread.Abort is perfectly safe to use if the current thread aborts *itself*. (See [MSDN](https://msdn.microsoft.com/en-us/library/ty8d3wta(v=vs.110).aspx): When a thread calls Abort on itself, the effect is similar to throwing an exception; the ThreadAbortException happens immediately, and the result is predictable.) In any case, as I mentioned in my note, it's ASP.NET framework code that's calling Thread.Abort, not my own code. – Michael Liu Jan 11 '18 at 20:48
  • @JonathonChase: I don't think this question is a duplicate of that question because that question doesn't mention await, which is required to reproduce the behavior I'm seeing. – Michael Liu Jan 11 '18 at 20:53
  • 1
    Fair point, the issue does seem isolated to the presence of the await in the finally. Calling Thread.ResetAbort() in the catch, or moving the await below the finally block prints as expected. – Jonathon Chase Jan 11 '18 at 21:34
  • 1
    For what its worth, in VS for Mac build 7.3.2 (Build 12) this is not reproducible. The output with `await` is `catch finally`. – InBetween Jan 11 '18 at 21:39
  • Looks like support for await in catch/finally blocks wasn't added until C#6, and previously it wasn't supported due to a CLR limitation. Also, if you add an await Task.Yield() to the catch portion, it won't fire off the Console.WriteLine either. My best guess is this is behavior of the run-time regarding `ThreadAbortException`s. – Jonathon Chase Jan 11 '18 at 22:35
  • 1
    And [this blog entry](https://clivetong.wordpress.com/2015/07/22/c-await-inside-catch-and-finally-leads-to-some-interesting-semantics/) seems to tie it together with the feature. The compiler may be removing the CLR finally in favor writing everything into a single catch for the await, but the ThreadAbortException is getting rethrown too early for it to be executed. – Jonathon Chase Jan 11 '18 at 22:40
  • @JonathonChase: In my actual app, I have two methods with finally blocks containing awaits. In one, the finally block is executed on ThreadAbortException; in the other, the finally block is not. I'm still trying to figure out what the difference is. – Michael Liu Jan 11 '18 at 22:46
  • @MichaelLiu I'm not totally sure, but if you replace `Thread.CurrentThread.Abort();` with `await Task.Run(() => Thread.CurrentThread.Abort());` the finally will fire. I'm not 100% sure what to do with that information, but it may help you determine the distinction between your two methods behavior. – Jonathon Chase Jan 11 '18 at 22:54
  • @JonathonChase: I've updated the question with an example (change #1) where the finally block **is** executed on ThreadAbortException even though it contains an await. Very mysterious. – Michael Liu Jan 11 '18 at 23:47

0 Answers0