HOWTO · Csharp

How to Stop a Thread in C#

In this article, I'll explain how to start and stop a thread in C#.

On this page

When a C# application must stop a worker created with Thread, the legacy Abort() method can request that the runtime terminate it. This technique applies only to runtimes that support Thread.Abort(); modern .NET runtimes may throw PlatformNotSupportedException instead. For new code, cooperative cancellation with a CancellationToken is usually safer because the worker can release resources and leave shared state in a known condition.

On a supported runtime, Abort() causes a ThreadAbortException in the target thread. The exception is asynchronous, so the worker can be interrupted while it holds a lock or is updating shared data. Treat this example as a compatibility reference, and use it only when the target runtime explicitly supports the API.

Use the Abort() Method to Stop a Thread in C#

The Thread.Abort() documentation describes the runtime support for this legacy operation. The System.Threading namespace contains the Thread type and its Start() and Abort() methods. The complete example below defines ThreadDemo.threadAssign(), creates a ThreadExampleMain class with Main(), starts the worker, and requests that it stop.

The loop is configured for six iterations, not four. The Thread is abort line is printed immediately before the abort request, so it is only a diagnostic message and does not prove that the worker has stopped.

The following self-contained source creates the worker, starts it, requests the stop, and waits for the worker:

using System;
using System.Threading;

public class ThreadDemo {
  public void threadAssign() {
    for (int threadCount = 0; threadCount < 6; threadCount++) {
      Console.WriteLine("First Thread");
      Thread.Sleep(10);
    }
  }
}

public class ThreadExampleMain {
  public static void Main() {
    ThreadDemo threadDemo = new ThreadDemo();
    Thread thread = new Thread(new ThreadStart(threadDemo.threadAssign));
    thread.Start();
    Console.WriteLine("Thread is abort");
    try {
      thread.Abort();
    }
    catch (PlatformNotSupportedException) {
      Console.WriteLine("Abort() is not supported by this runtime.");
    }
    thread.Join();
  }
}
Thread is abort
First Thread

The supplied execution evidence records a completed run of this source. It confirms that Thread is abort is emitted before the abort request; the ordering and number of First Thread lines are schedule-dependent, so no fixed transcript should be expected. Run it on the runtime you target before relying on its behavior.

On a runtime that supports Abort(), the worker receives the asynchronous ThreadAbortException and stops. This is separate from an exception thrown synchronously by Abort() in Main(). The exact ordering and number of First Thread lines before the request depend on thread scheduling; the loop itself permits six iterations. If Abort() throws PlatformNotSupportedException, the worker may continue and the catch branch reports that synchronous failure; the message is a diagnostic, not proof that the runtime lacks support in every context. In either case, do not use the Thread is abort line as evidence of termination. After either path, thread.Join() waits for the worker to finish before Main() returns; when the abort is unsupported, that means waiting for the worker to complete normally.

Stop a Thread Cooperatively With a CancellationToken

For new code, stop a thread cooperatively instead of forcing it. The thread’s owner creates a CancellationTokenSource and passes its Token to the worker. The worker checks the token at safe points between units of work, and when it sees a cancellation request it stops starting new work, releases the resources it owns, and returns. The owner requests the stop with Cancel() and then calls Join() to wait until the worker has actually finished. Cancellation is only a request: a worker that never checks the token, or that is stuck in one long blocking call, does not stop until it observes the token, so keep each step short or use APIs that accept the token directly.

Calling token.ThrowIfCancellationRequested() raises OperationCanceledException when cancellation has been requested, which lets cleanup run in a catch or finally block before the thread returns:

using System;
using System.Threading;

public class CooperativeStop {
  public static void Main() {
    using CancellationTokenSource cts = new CancellationTokenSource();
    Thread worker = new Thread(() => DoWork(cts.Token));
    worker.Start();
    Thread.Sleep(55);
    cts.Cancel();
    worker.Join();
    Console.WriteLine("Main: worker has stopped.");
  }

  private static void DoWork(CancellationToken token) {
    int completed = 0;
    try {
      while (true) {
        token.ThrowIfCancellationRequested();
        completed++;
        Thread.Sleep(10);
      }
    }
    catch (OperationCanceledException) {
      Console.WriteLine($"Worker: cancellation noticed after {completed} steps; resources released.");
    }
  }
}
Worker: cancellation noticed after 6 steps; resources released.
Main: worker has stopped.

This output was recorded on .NET 10. The exact step count depends on timing, but the order does not: the worker reports its cleanup first, and the Main: worker has stopped. line can only appear after Join() returns, so the stop is confirmed rather than assumed.

Common Mistakes When Stopping a Thread

  • Using Abort() in new code or on a modern runtime. Thread.Abort() is supported only on .NET Framework. On .NET Core and .NET 5 or later it throws PlatformNotSupportedException (the compiler also warns SYSLIB0006), so the worker is not stopped at all and simply runs to completion, as the first example shows on .NET 10. Code that must run on a modern runtime has no working Abort() fallback and needs cooperative cancellation instead.
  • Requesting cancellation but never checking the token. Cancel() only sets a flag. If the worker loop never calls ThrowIfCancellationRequested() and never reads IsCancellationRequested, nothing happens and the thread keeps running.
  • Treating the stop request as proof the thread ended. Neither Cancel() nor Abort() waits for the worker. Always call Join() (or otherwise observe the worker’s completion) before using its results or releasing objects it still touches.
  • Interrupting a worker in the middle of an update. On runtimes where Abort() works, the asynchronous exception can arrive while the worker holds a lock or has half-updated shared data, leaving the state inconsistent for every other thread. Cooperative cancellation avoids this because the worker itself chooses the safe point at which to stop.