0

Since the Thread.Abort method is not supported by Microsoft anymore. What is the correct way to get past an operation that is never (or within specified time) expected to complete. The code with Thread.Abort that I have written is below:

try
        {
            CancellationTokenSource cts = new CancellationTokenSource();                
            Task t = Task.Run(() =>
            {
                TClass c1 = new TClass();

                using (cts.Token.Register(Thread.CurrentThread.Abort))
                {
                    c1.Generate();
                }
            }, cts.Token);
            TimeSpan ts = TimeSpan.FromSeconds(5);
            if (!t.Wait(ts))
            {
                cts.Cancel();                  
                throw new Exception("Test success");
            }
        }
        catch (Exception e)
        {                
            Console.WriteLine(e.Message);                             
        }  
}

Note: c1.Generate() represents a method that we wish to abort if it does not complete within specific time.

Reference: https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.abort?view=net-6.0

Edit:

Added code for TClass [only a representation of method that will run forever, real world it's a third party library method that we can't alter]:

public class TClass:IDisposable
{
    public Thread Thread { get { return Thread.CurrentThread; } }
    public TClass()
    {

    }

    public void Dispose()
    {
        GC.SuppressFinalize(this);
    }

    public void Generate()
    {
        int x = 0;
        while (true)
        {
            Console.WriteLine(x++);
            Task.Delay(TimeSpan.FromSeconds(1)).Wait();
        }
    }
}
Shashank Chaturvedi
  • 2,756
  • 19
  • 29
  • Can you modify the `c1.Generate` method to be `async` itself and/or accept a cancellation token? – gunr2171 Apr 07 '22 at 20:17
  • 1
    How to do it without Thread.Abort is [explained here](https://learn.microsoft.com/en-us/dotnet/standard/threading/canceling-threads-cooperatively). – Robert Harvey Apr 07 '22 at 20:17
  • As already mentioned in the post I don't have access to the actual operation. This is a sample code that represents the scenario. Actual code generates a PDF file, and the write to disk operation takes forever (sometimes only). – Shashank Chaturvedi Apr 07 '22 at 21:02
  • @ShashankChaturvedi why are you using Thread.Abort at all? Check the CancellationToken instead and if `CancellationToken.IsCancellationRequested` is true, exit with a simple `return`. That means you need to pass the CancellationToken to `Generate` – Panagiotis Kanavos Apr 08 '22 at 14:29
  • The question's code can be reduced to just two lines, no aborts and no exceptions. Is this how the *actual* code looks like? Or is the real problem more complex? If there's no loop and no asynchronous code, the problem becomes harder because you can't check the token. If the method contains a lot of operations to build a PDF though, you can check the token between operations. You can also just pass the token to `Task.Run` and deal with the exception and the cleanup – Panagiotis Kanavos Apr 08 '22 at 14:49

3 Answers3

3

The right way to abort a non-cancelable method that will never complete, is to run this method on a separate process, and then cancel it by killing the process. Hopefully this method has no return value, because otherwise you will have to get this value through inter-process communication, which is not trivial.

Another possible solution that is not guaranteed to work, is to use the Thread.Interrupt method. This only works if you know the thread on which the method is running, and that thread transitions periodically (or permanently) to a sleeping state. Some operations, like asynchronous methods, are not running on threads, so it's not guaranteed that the operation will terminate, even if you kill the thread that started this operation by interrupting it.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • You can just say there is no elegant way to do this lol. I can't create a separate a process just for one operation and have IPC between the two. That will be a performance and architectural nightmare. – Shashank Chaturvedi Apr 07 '22 at 21:00
  • @ShashankChaturvedi you asked for the right way, not for the pretty, easy or elegant way. You should be thankful that an ugly solution even exists, because your problem is ugly too. Essentially you are writing a program whose internal parts refuse to cooperate with each other. So you must have the healthy parts kill the cancerous parts, in order for the whole organism to survive. That's not gonna be pretty! – Theodor Zoulias Apr 07 '22 at 21:09
  • Please try and understand what I have above is well written elegant code that works fine with .net Framework (so elegance is implied). But Microsoft decided to deprecate Thread.Abort in .net 5. Although it can still be used in .net 5 (refer: https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.abort?view=net-6.0). – Shashank Chaturvedi Apr 07 '22 at 21:48
  • 1
    @ShashankChaturvedi the `Thread.Abort` can not be used on .NET Core and later platforms. The API exists, but it throws `PlatformNotSupportedException` at runtime. If you would like to see it coming back, please read [this comment](https://github.com/dotnet/runtime/issues/11369#issuecomment-692155140) on GitHub by jkotas (leading Microsoft engineer). Or [this](https://github.com/dotnet/runtime/issues/66943#issuecomment-1073896927) (more recent). – Theodor Zoulias Apr 07 '22 at 21:56
1

The recommended way (especially for ASP.NET Core) is now to use Tasks and "abort" them with CancellationTokens. You can learn more about how to use CancellationTokens here.

Sebastian Siemens
  • 2,302
  • 1
  • 17
  • 24
  • 2
    I might be missing something with this explanation, but I _do_ see Tasks and CancellationTokens in the OP's code. – gunr2171 Apr 07 '22 at 20:12
  • 1
    @gunr2171: Yes, so the change needs to be "don't abort the thread; use the CancellationToken to end the thread gracefully." – Robert Harvey Apr 07 '22 at 20:26
  • I will really appreciate a working sample code. I could have used Token.IsCancellationRequested and gracefully ended the task if I had access to the method that I am calling in the task. – Shashank Chaturvedi Apr 07 '22 at 20:38
0

You already use a CancellationTokenSource. Use its CancellationToken to check whether cancellation is signaled instead of aborting. The thread you're trying to abort is a threadpool thread that's meant to be used by multpiple tasks.

Generate should change to :

public void Generate(CancellationToken token)
{
    int x = 0;
    while (token.IsCancellationRequested)
    {
        Console.WriteLine(x++);
        Task.Delay(TimeSpan.FromSeconds(1)).Wait();
    }
}

Although blocking like this isn't a good idea. This is little better than Thread.Sleep. The method should be async :

public async Task Generate(CancellationToken token)
{
    int x = 0;
    while (token.IsCancellationRequested)
    {
        Console.WriteLine(x++);
        await Task.Delay(TimeSpan.FromSeconds(1),token);
    }
}

The calling code can be simplified to just :

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));                
await Task.Run(async ()=>{
    var c1 = new TClass();
    await c1.Generate(cts.Token);
},cts.Token);

If you only want to exit grafecully and waiting for the delay to expire is OK, you can change the code to :

var cts = new CancellationTokenSource(TimeSpan.FromSeconds(1));                
await Task.Run(async ()=>{
    var c1 = new TClass();
    await c1.Generate(cts.Token);
});

And Generate to :

public async Task Generate(CancellationToken token)
{
    int x = 0;
    while (token.IsCancellationRequested)
    {
        Console.WriteLine(x++);
        await Task.Delay(TimeSpan.FromSeconds(1));
    }
}
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236