NDepend Blog

Improve your .NET code quality with NDepend

On replacing Thread.Abort() in .NET Core

May 19, 2021 3 minutes read

On replacing Thread.Abort() in .NET 6 .NET 5 .NET Core

Thread.Abort() is not supported in .NET 5 / .NET Core

We are actually migrating the NDepend analysis and reporting to .NET 5 and figured out that there is no equivalent for the infamous API Thread.Abort(). By the time ASP.NET Core was created, CancellationToken had become the safe and widely accepted alternative to Thread.Abort(), so there was no need to implement it in the first version of .NET Core. Interestingly enough Thread.Abort() is a .NET Standard API but it throws NotSupportedException in .NET 5 / .NET Core context.

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. Careful usage of Thread.Abort() is not-trivial and implies some events to prevent edge cases to happen. But for more than a decade it worked (and still works) fine for thousands of real users. Till now we applied the tenet if it ain’t broke, don’t fix it.

CancellationToken cannot handle preemptive cancellation scenarios

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.

However CancellationToken doesn’t support preemptive cancellation scenarios, made possible by the Thread.Abort() API. Preemptive cancellation is useful to cancel long-running operation involving code that we do not own or that makes awkward to periodically check for cancellation. We have a few such timeout scenarios, the most notable being the execution of code queries compiled on the fly: how to inject periodically checks in a generated LINQ query? We will inject calls to such method CheckTimeOut() (below) in the generated LINQ query but the whole challenge will be to do it in a way that won’t harm performances:

Eric Lippert advised in this stackoverflow answer to handle such scenario with a dedicated child process that can be safely killed if needed. Unfortunately this suggestion does not apply well to our needs since both the query execution context and the query result aren’t lightweight in many scenarios.

As a consequence some significant work is now required to refactor our tricky but reliable preemptive cancellation implementation into a well-designed co-operative cancellation implementation.

Using CancellationToken to timeout a synchronous processing

Usually CancellationToken is used when the cancelable operation is processed asynchronously on a worker thread of the CLR thread pool. But in our scenarios the cancellable processing is executed synchronously on the main caller thread. To do so a thread from the CLR pool used to be responsible for calling Thread.Abort() against the main thread upon timeout. It sounds dangerous but again, when done carefully with some events it works seamlessly.

Hopefully CancellationToken can be used to timeout a synchronous processing and here is how. Thanks to the CancellationTokenSource(TimeSpan) constructor this implementation doesn’t even involve any secondary thread to watch for timeout. Undoubtedly this code is way cleaner than anything based on Thread.Abort() 🙂