1

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

 2 years ago
source link: https://blog.ndepend.com/on-replacing-thread-abort-in-net-6-net-5-and-net-core/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

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:

public static T CheckTimeOut(this T t, ICQLinqExecutionContext context) {
   if(context.CancellationTokenSource.IsCancellationRequested) {
      throw new TaskCancelledException();
   return t;

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() 🙂

using System;
using System.Threading;
using System.Threading.Tasks;
namespace ClassLibraryNetStandard {
   static class Program {
      internal static void Main() {
         bool b = WaitFor.TryCallWithTimeout(
            OneSecondMethod,
            500.ToMilliseconds(), // timeout
                                  //  500ms => OneSecondMethod() gets Cancelled
                                  // 1500ms => OneSecondMethod() gets Executed
            out int result);
         Console.WriteLine($"OneSecondMethod() {(b ? "Executed" : "Cancelled")}");
      static int OneSecondMethod(CancellationToken ct) {
         for (var i = 0; i < 10; i++) {
            Thread.Sleep(100.ToMilliseconds());
            // co-operative cancellation implies periodically check IsCancellationRequested
            if (ct.IsCancellationRequested) { throw new TaskCanceledException(); }
         return 123; // the result
      static TimeSpan ToMilliseconds(this int nbMilliseconds)
         => new TimeSpan(0, 0, 0, 0, nbMilliseconds);
   static class WaitFor {
      internal static bool TryCallWithTimeout<TResult>(
            Func<CancellationToken, TResult> proc,
            TimeSpan timeout,
            out TResult result) {
         // Request cancellation after a duration of 'timeout'
         var cts = new CancellationTokenSource(timeout);
            result = proc(cts.Token);
            return true;
         } catch (TaskCanceledException) { }
         finally { cts.Dispose(); }
         result = default;
         return false;

My dad being an early programmer in the 70's, I have been fortunate to switch from playing with Lego, to program my own micro-games, when I was still a kid. Since then I never stop programming.

I graduated in Mathematics and Software engineering. After a decade of C++ programming and consultancy, I got interested in the brand new .NET platform in 2002. I had the chance to write the best-seller book (in French) on .NET and C#, published by O'Reilly (> 15.000 copies) and also did manage some academic and professional courses on the platform and C#.

Over the years, I gained a passion for understanding structure and evolution of large complex real-world applications, and for talking with talented developers behind it. As a consequence, I got interested in static code analysis and started the project NDepend.

Today, with more than 8.000 client companies, including many of the Fortune 500 ones, NDepend offers deeper insight and understanding about their code bases to a wide range of professional users around the world.

I live with my wife and our twin babies Léna and Paul, in the beautiful island of Mauritius in the Indian Ocean.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK