4

I'm starting a thread like this:

nameOfThread = new Thread(() => 
{
    //do stuff
});
nameOfThread.Start();

At some point inside this anonymous function I open a WinSCP session like this:

using (Session session = new Session())
{
     //do stuff
}

If I abort the thread (from somewhere else) like this nameOfThread.Abort() while still doing stuff inside using, is the session disposed at the end?

Angelo
  • 183
  • 1
  • 1
  • 10
  • 2
    Why abor the thread? Use a CancellationTokenSource and gracefully signal to the worker method that it needs to terminate – Panagiotis Kanavos Nov 27 '18 at 13:27
  • Yes, whether aborted or not the ```using``` statement still disposes properly. – Michael Puckett II Nov 27 '18 at 13:27
  • Why are you using a thread directly instead of eg Task.Run? – Panagiotis Kanavos Nov 27 '18 at 13:28
  • If you have a problem and think `Thread.Abort` is part of the solution, you've found the wrong solution. Heed the warnings in the [documentation](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.abort?view=netframework-4.7.2) – Damien_The_Unbeliever Nov 27 '18 at 13:29
  • @PanagiotisKanavos because I'm not really a savy when it comes to multithreading. but this doesn't answer the question – Angelo Nov 27 '18 at 13:30
  • @Mr.Howell That is why it is a comment and not an answer... – Patrick Hofman Nov 27 '18 at 13:30
  • @Mr.Howell using a CancellationTokenSource is the actual answer. Don't abort the thread, they are expensive and ThreadAbortExceptions can't be caught. Use a CTS so your code can terminate gracefully – Panagiotis Kanavos Nov 27 '18 at 13:30
  • @Mr.Howell a *lot* of .NET methods accept a cancellation token too (eg database commands), which means you can cancel whatever blocking operation is running at the moment without nuking the thread – Panagiotis Kanavos Nov 27 '18 at 13:32
  • @PanagiotisKanavos alright man, i will look into it 'cause i clearly don't know enough about this stuff, thanks – Angelo Nov 27 '18 at 13:32

2 Answers2

3

Most likely it will, but you can't be sure.

According to the documentation:

When this method [Abort] is invoked on a thread, the system throws a ThreadAbortException in the thread to abort it.

And we know exceptions will still let using statements dispose, as they should. (Give and take a few exceptions)

On the other hand, if you can end the thread gracefully, for example with a CancellationTokenSource, it would be a lot nicer for your app. It will offer much more control over the actual termination of your thread and the handling of exceptions.

Patrick Hofman
  • 153,850
  • 22
  • 249
  • 325
  • Alright, thanks for the answer and alternative. I will study multi threading more. – Angelo Nov 27 '18 at 13:35
  • Keep in mind that the thread could call `ResetAbort` and prevent the effect of the `ThreadAbortException`. – Brian Rasmussen Nov 27 '18 at 13:39
  • 1
    You'll find the inconvenient truth in [this post](https://stackoverflow.com/a/14831590/17034). – Hans Passant Nov 27 '18 at 13:46
  • @HansPassant This should no longer be true. I remember reading this was solved in earlier versions of .NET, not sure which, where now the ```try / finally``` is nested in another ```try / finally``` with some other thread management to care for it. Not to confuse the OP but it's a bit of behind the scenes work to make sure this doesn't happen anymore. Looking at it, as is, without the race condition should be resolved as accurate and thread safe in current .NET releases. I'll have to look back into it to see when this was updated. – Michael Puckett II Nov 27 '18 at 13:49
  • That's an interesting tidbit that deserves better annotation, given that this does not yet happen on my machine in .NET 4.7.2 and C# v7.3. – Hans Passant Nov 27 '18 at 13:53
  • @HansPassant So the bottom line is: don't ever dare to use `Thread.Abort()`. Your post there gives a lot of reasons to stay away from it. Thanks. – Patrick Hofman Nov 27 '18 at 13:58
  • Not trying to poke you in the eye too much, but proper advice probably should resemble "no, you can't be sure". You can make the OP feel better with "don't worry about it, the finalizer will take care of it". – Hans Passant Nov 27 '18 at 14:06
  • @HansPassant I am not that sensitive. I appreciate your help. Updated. – Patrick Hofman Nov 27 '18 at 14:11
  • @PatrickHofman can I end a task at any time I want with CancellationTokenSource though? From what I understand you put the condition in your function but what if an action takes like one hour? I want to end the task now but I have to wait one hour? – Angelo Nov 27 '18 at 14:35
  • You can set a timeout to end it when it doesn't end itself. At least you give it the chance to end gracefully. – Patrick Hofman Nov 27 '18 at 14:36
  • I see, for my particular situation the task is monitoring something and is basically blocked for hours, so putting timeout is a bit redundant since in 99% of the cases 1 minute wait time won't matter. I should use `Abort` in this situation I assume – Angelo Nov 27 '18 at 14:38
  • @HansPassant I apologize and stand corrected. I have dug and cannot remember when or why I thought this but I am wrong and you are correct. The ```using``` statement does have a race condition issue when dealing with aborted threads (or any other exceptions that could occur). – Michael Puckett II Nov 28 '18 at 03:02
1

I answered you can guarantee that the using statement will always call Dispose and I stand corrected, I was wrong.

There is a potential race condition with the using statement that doesn't guarantee disposing and I've put together a console app illustrating this (which isn't hard or trivial).

I was correct when showing how the IL generates using like so:

var session = new Session(); //If this causes an error or abort happens during initialization then we don't enter try
//If abort is called here then we never enter try
//In either case above we may have undisposed resources initialized at this point
try
{
    //do stuff
}
finally
{
    session.Dispose();
}   

However; note the comments where I show the race condition that may occur if aborted before entering try.

Here is a console app written just to prove this point. The first works as expected but if you add the commented out code //thread.Abort() when we initialize R then you will see it init but never dispose :/

using System;
using System.Threading;

namespace Question_Answer_Console_App
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Start Main");

            Thread thread = null;
            thread = new Thread(new ThreadStart(() =>
            {
                Console.WriteLine("Thread Started");
                using (var r = new R(thread))
                {
                    Console.WriteLine($"Using {nameof(R)}");
                }
            }));

            thread.Start();
            thread.Join();

            Console.WriteLine("End Main");
            Console.ReadKey();
        }
    }

    public class R : IDisposable
    {
        public R(Thread thread)
        {
            Console.WriteLine($"Init {nameof(R)}");
            //thread.Abort();
        }

        public void Dispose()
        {
            Console.WriteLine($"Disposed {nameof(R)}");
        }
    }
}

Output with //thread.Abort() commented out:

Start Main
Thread Started
Init R
Using R
Disposed R
End Main

Output with thread.Abort() not commented out:

Start Main
Thread Started
Init R
End Main
Michael Puckett II
  • 6,586
  • 5
  • 26
  • 46
  • I don't think the question is about how `using` works, but how a thread abort has effect on it. – Patrick Hofman Nov 27 '18 at 13:39
  • @PatrickHofman You're correct. Maybe I should have explained better but I did mention that a thread, even when aborted, will always call ```finally``` and since it's basically translated into a ```try finally``` it will dispose when aborted. – Michael Puckett II Nov 27 '18 at 13:42
  • @PatrickHofman I updated the answer at the bottom to hopefully make it more clear. – Michael Puckett II Nov 27 '18 at 13:45