36

Background

I have a Service abstraction. Each service has it own WorkItem. WorkItem able to start with some data. The service is limiting the excution time of WorkItem. Let's say that a single workitem can takes up to 60 seconds. After this, the Service should kill it.

This code migrated from the .NET Framework, I created a Thread object which run the Start(model) method. Then the code was something like:

Thread t = new Thread(workItem.Start, model);
t.start();
if (!t.Join(TimeSpan.FromSeconds(60)))
    t.Abort();

The Thread.Abort was injecting an exception for the running thread, which lead it for immediately stop.

Now, I moved the code to dotnet core - as you may know, when you calling Thread.Abort() your getting the following message:

System.PlatformNotSupportedException: Thread abort is not supported on this platform.
   at System.Threading.Thread.Abort()
   at ...

The Goal

I want to limit the execution time of the WorkItem to specific amount of time. Note that this limitation should work also if you running code line like this:

Thread.Sleep(61000); // 61 seconds. should be stop after 60 seconds.

Progress

On the dotnet core world, it's seems like it's going to the Task related solution. So, I thought to use CancellationToken. But its seems like its impossible to watch the "Canceled" event and stop immediately. The examples I saw are using while (!canceled) loops, which cant stop long operations (like Thread.Sleep(1000000).

Question

How to do it right?

Update

I written this sample code:

public static bool ExecuteWithTimeLimit(TimeSpan timeSpan, Action codeBlock)
{
    try
    {
        Task task = Task.Factory.StartNew(() => codeBlock());
        if (!task.Wait(timeSpan))
        {
            // ABORT HERE!
            Console.WriteLine("Time exceeded. Aborted!");
        }
        return task.IsCompleted;
    }
    catch (AggregateException ae)
    {
        throw ae.InnerExceptions[0];
    }
}

And this Main file:

public static void Main(string[] args)
{
    bool Completed = ExecuteWithTimeLimit(TimeSpan.FromMilliseconds(2000), () =>
    {
        Console.WriteLine("start");
        Thread.Sleep(3000);
        Console.WriteLine("end");
    });

    Console.WriteLine($"Completed={Completed}");
    Console.ReadLine();
}

Expected: "end" wont be printed to the screen. Actual: "end" printed. Is there any alternative that can kill a Task?

svick
  • 236,525
  • 50
  • 385
  • 514
No1Lives4Ever
  • 6,430
  • 19
  • 77
  • 140
  • Try [Task.Delay](https://learn.microsoft.com/en-US/dotnet/api/system.threading.tasks.task.delay?view=netframework-4.7.2#System_Threading_Tasks_Task_Delay_System_Int32_System_Threading_CancellationToken_) instead of `Thread.Sleep` – H.G. Sandhagen Nov 25 '18 at 07:52
  • 1
    You know that to do it right you simply don't abort threads... Was never good idea to start with. Probably should be just closed as duplicate of https://stackoverflow.com/questions/39243016/cant-find-abort-method-in-thread – Alexei Levenkov Nov 25 '18 at 07:53
  • @H.G.Sandhagen it not going to help with code that actually hogs CPU for long time rather than sleeps... Or uses sync network calls... – Alexei Levenkov Nov 25 '18 at 07:54
  • 1
    @H.G.Sandhagen - `Thread.Sleep` is just example. I can think of other examples which not supporting the async model. Another example: downloading a file with `WebClient` and the server is really slow, therefore it takes too much time (exceed the limit). – No1Lives4Ever Nov 25 '18 at 07:56
  • @AlexeiLevenkov - Not sure if it "smart" idea or not. This is needed when you process different types of data which leads to different execution time. The alternative to `Thread.Abort` is to create a seperated process for each `WorkItem` and kill it if running to much time - sounds to me too much resources will be consumed. – No1Lives4Ever Nov 25 '18 at 07:59
  • A suspended thread does not consume CPU time. It does consume memory but is that an issue in this application? – Chris Rollins Nov 25 '18 at 09:52
  • @No1Lives4Ever this isn't about .NET Core vs .NET Framework. No matter the example, if you have to abort you're doing it wrong. It's not needed either. Events, semaphores, Mutexes, Monitors all exist so you *don't* have to abort. The docs warn against aborting since .NET Framework 1.0. Tasks were introduced in .NET Framework 4.0 not .NET Core, because aborting threads is dangerous and suspending threads is expensive. – Panagiotis Kanavos Jan 17 '22 at 14:10

8 Answers8

15

Use thread.Interrupt(); instead of Abort() method.

M Komaei
  • 7,006
  • 2
  • 28
  • 34
12

Without aborting the only solution is to poll the cancellation request often enough so after all the while (!canceled) solution you mentioned.

The examples I saw are using while (!canceled) loops, which cant stop long operations (like Thread.Sleep(1000000).

This is just partially true. For example, this can be re-written like this to be responsive:

 var timeout = TimeSpan.FromSeconds(60);
 var stopwatch = new Stopwatch();
 stopwatch.Start();

 while (!cancelToken.IsCancellationRequested
  && stopwatch.ElapsedMilliseconds < timeout)
{
    Thread.Sleep(10);
}

Of course, not every task can be easily re-written to poll the cancellation like this. If you are in a deep call chain it can be a pain to check the cancellation at every level. For that reason you can also use the CancellationToken.ThrowIfCancellationRequested method, which will throw an OperationCanceledException if there was a cancel request. I usually tend to not throwing an exception just for myself and using it for control flow but cancellation is one of the areas where it can be justified.

This is solution has of course some limitations compared to Abort:

  • You will not able to cancel 3rd party routines, which don't support cancellation and you cannot refactor them
  • The OperationCanceledException can be swallowed easily, whereas ThreadAbortException was always re-raised at the end of the catch blocks so a 3rd part library could be aborted by a good chance even if contained general catch blocks.

Update:

If you are confident/desperate enough you can use the ThreadEx.Abort method, which calls the Thread.AbortInternal by reflection. Though it is not guaranteed it will be a long-living solution in .NET Core.

Though I don't completely agree with making Thread.Abort obsolete as it was a good last-chance tool for shutting down routines on which you didn't have influence otherwise, I'm also at the side abortion must be avoided at all costs as it can have nasty side effects. If you are the author of the whole code base it can be always avoided.

Update 2:

It seems that AbortInternal has been removed since then. At least current .NET Core source does not contain such a method.

György Kőszeg
  • 17,093
  • 6
  • 37
  • 65
  • 2
    There seems to be no Thread.InternalAbort() in dotnet core 2.1. Is there an alternative? – nl-x Jul 16 '19 at 14:43
  • It doesn't seem to work for me. I can't see `AbortInternal` using reflection. Do you see it in 2.1 ? – nl-x Jul 17 '19 at 11:27
  • 1
    You are right, there is no `AbortInternal` in [current .NET Core](https://github.com/dotnet/corefx/blob/master/src/Common/src/CoreLib/System/Threading/Thread.cs) – György Kőszeg Jul 17 '19 at 17:34
9

You could use Thread.Interrupt(), which causes a ThreadInterruptedException() in the worker thread. You can catch the exception with a try catch, and after that safely join the thread with the main thread to clean up the worker thread. This would look like this:

Thread t = new Thread(workItem.Start, model);
t.Start();

// do other stuff or wait

t.Interrupt();
t.Join();

And the function of the worker thread looks like this:

try
{
   // stuff the worker thread needs to do
}
catch (Exception e)
{
   // go in here when interrupted
}

Waiting can then be implemented like this

Thread t = new Thread(workItem.Start, model);
t.Start();
if (!t.Join(TimeSpan.FromSeconds(60)))
{
    t.Interrupt();
    t.Join();
}

This is a way to (kind off) kill threads, but it is more clean to do it with CancelationTokens. I say kind of here as the thread won't get interrupted until it is blocked by the OS or some other block. So if the thread never blocks the exception is never thrown, and thus the thread might complete without ever being interrupted.

  • This does not work as expected. Place a line like "Enumerable.Range(0, int.MaxValue).Distinct().ToList();" inside the try-Block, and you'll see that the thread will continue. – Manuel R. Wenk Apr 14 '22 at 07:30
  • From [Microsoft](https://learn.microsoft.com/en-us/dotnet/api/system.threading.thread.interrupt?view=net-6.0) "If this thread is not currently blocked in a wait, sleep, or join state, it will be interrupted when it next begins to block". The same thing would happen with that piece of code if you use cancelation tokens, as the cancellation token will only be checked when the programmer says so. But like I stated in the answer it is not the best solution for this and cancelation tokens are way better, but it works sort off. – Sietze Riemersma Apr 15 '22 at 13:52
  • Yeah, but this makes the solution not equivalent to thread.Abort() - you can't abort an atomic long runnig operation before it's finished. – Manuel R. Wenk Apr 16 '22 at 16:06
  • 1
    The same can be said about cancellation tokens, they are not equivalebt and there is no equivalent solution to thread.Abort. – Sietze Riemersma Apr 17 '22 at 19:45
  • 3
    NOTE: The fact that the thread **won't get interrupted until it blocks** should be part of the answer (not buried in a comment). – ToolmakerSteve Jul 04 '22 at 21:12
  • 2
    I added an explanation to the answer @ToolmakerSteve – Sietze Riemersma Jul 05 '22 at 09:27
3

Four years later, there is now an equivalent in net7 !

Non-cooperative abortion of code execution: ControlledExecution

Be careful, this method might corrupt the process, see documentation.

You can read about why and how such a method came back to .NET here: https://github.com/dotnet/runtime/issues/41291 https://github.com/dotnet/runtime/discussions/66480

Goswin Rothenthal
  • 2,244
  • 1
  • 19
  • 32
  • 2
    *"The `ControlledExecution.Run(Action, CancellationToken)` method might corrupt the process and should not be used in production code. This method runs code that can be aborted asynchronously. While this method is new for .NET 7, it's also marked as obsolete to discourage you from using it."* [Link](https://learn.microsoft.com/en-us/dotnet/fundamentals/syslib-diagnostics/syslib0046) – Theodor Zoulias Jan 08 '23 at 20:59
2

From experience: When Thread.Abort was made obsolete, we looked around. Thread.Interrupt was not of use to us.

Our final decision - refactor code to run some code in its own process and then we can use Process.Kill.

Background on why we do rude interruptions on code: Our system is used in manufacturing process automation and when someone hits an emergency stop button, the standard is to stop whatever the code is doing within 100ms. Our code sends a request to shutdown to the process (which may be running a third-party driver that does not listen for aborts) and, if the process does not shut down in 50ms, we do a Process.Kill(true). Frankly, for an emergency stop, we do not care if the system gets corrupted - we're going to rebuild the processes, anyway. In extreme situations, we will reboot the computer if we can save a life.

We also recognize that this is an edge situation and most code never needs to do rude interruptions.

RobCole
  • 90
  • 8
0

Thread.Abort() used to work when carefully Handled. There is no discussion: Thread.Abort() is a dangerous API that throws ThreadAbortException at any random point deep in the call stack. Nevertheless production logs show that when carefully implemented Thread.Abort() doesn’t provoke any crash nor state corruption.

CancellationToken is nowadays the safe way to implement cancelable operations. But it is not a replacement for Thread.Abort(): it only supports co-operative cancellation scenarios, where the cancellable processing is responsible for periodically checking if it has been cancelled.

if(cancelToken.IsCancellationRequested){
   throw new TaskCancelledException();
}

Update

As suggested by @Theodor, Same result can be achieved by using

cancelToken.ThrowIfCancellationRequested();

This function implements the same logic as above

you can then handle the thrown exception as follows

try
  {
    await YourTask(cancellationToken);
  }
  catch (OperationCanceledException ex) // includes TaskCanceledException
  {
    MessageBox.Show("Your submission was canceled.");
  }
Bilal Bin Zia
  • 596
  • 3
  • 12
0

Quoting an answer by a dotnet/runtime collaborator, in a recent GitHub issue.

Aborting threads without asking them is a very dangerous practice and has not been supported by design in modern .NET since .NET Core 1.0, which is why I will close this issue.

The safe alternative is to pass CancellationTokens around in your thread and mark the points that are OK to abort, yourself by calling CancellationToken.ThrowIfCancellationRequested. A nice rule of thumb is to call this method at the beginning of a loop, but perhaps not all loops, you have to make a balance; if you call it too often performance will decrease, and if you call it too rarely code might not stop immediately.

If the thread you want to abort is performing I/O, you can cancel it by switching to asynchronous methods and the async and await keywords and passing to them that CancellationToken.

But either way your code needs some refactoring.

(teo-tsirpanis, Feb 18, 2022)

So it seems that officially there is no equivalent API in .NET Core and later.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
0

Just my two cents.

As per my readings from this book Parallel Programming and Concurrency with C# 10 and .NET 6

Generally, destroying a managed thread is considered an unsafe practice. That is why .NET 6 no longer supports the Thread.Abort method. In .NET Framework, calling Thread.Abort on a thread would raise a ThreadAbortedException exception and stop the thread from running. Aborting threads was not made available in .NET Core or any of the newer versions of .NET. If some code needs to be forcibly stopped, it is recommended that you run it in a separate process from your other code and use Process.Kill to terminate the other process.

You can use Cancellation Tokens in conjenction with Tasks as well.

VivekDev
  • 20,868
  • 27
  • 132
  • 202