HOWTO · Csharp

Cómo detener un hilo en C#

En este artículo, explicaré cómo iniciar y detener un hilo en C#.

En esta página

Cuando una aplicación de C# debe detener un trabajador creado con Thread, el método heredado Abort() puede solicitar al entorno de ejecución que lo termine. Esta técnica solo se aplica a los entornos de ejecución que admiten Thread.Abort(); los entornos modernos de .NET pueden lanzar PlatformNotSupportedException en su lugar. Para el código nuevo, la cancelación cooperativa con un CancellationToken suele ser más segura, porque permite que el trabajador libere recursos y deje el estado compartido en una condición conocida.

En un entorno compatible, Abort() provoca una ThreadAbortException en el hilo de destino. La excepción es asíncrona, por lo que el trabajador puede interrumpirse mientras mantiene un bloqueo o actualiza datos compartidos. Considere este ejemplo como una referencia de compatibilidad y úselo únicamente cuando el entorno de ejecución de destino admita explícitamente la API.

Utilice el método Abort() para detener un subproceso en C#

La documentación de Thread.Abort() describe la compatibilidad del entorno de ejecución con esta operación heredada. El espacio de nombres System.Threading contiene el tipo Thread y sus métodos Start() y Abort(). El ejemplo completo siguiente define ThreadDemo.threadAssign(), crea una clase ThreadExampleMain con Main(), inicia el trabajador y solicita que se detenga.

El bucle está configurado para realizar seis iteraciones, no cuatro. La línea Thread is abort se imprime inmediatamente antes de la solicitud de abortar, por lo que solo es un mensaje de diagnóstico y no demuestra que el trabajador se haya detenido.

El siguiente código fuente autónomo crea el trabajador, lo inicia, solicita su detención y espera a que termine:

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

La evidencia de ejecución suministrada registra una ejecución completada de este código fuente. Confirma que Thread is abort se emite antes de la solicitud de abortar; el orden y la cantidad de líneas First Thread dependen de la planificación, por lo que no debe esperarse una transcripción fija. Ejecútelo en el entorno de ejecución de destino antes de basarse en su comportamiento.

En un entorno que admita Abort(), el trabajador recibe la ThreadAbortException asíncrona y se detiene. Esto es independiente de una excepción lanzada sincrónicamente por Abort() en Main(). El orden exacto y la cantidad de líneas First Thread que aparecen antes de la solicitud dependen de la planificación de los hilos; el bucle permite seis iteraciones. Si Abort() lanza PlatformNotSupportedException, el trabajador puede continuar y la rama catch informa de ese error sincrónico; el mensaje es un diagnóstico, no una prueba de que el entorno carezca de compatibilidad en todos los contextos. En cualquier caso, no use la línea Thread is abort como evidencia de terminación. Después de cualquiera de las dos rutas, thread.Join() espera a que el trabajador termine antes de que Main() devuelva el control; si Abort() no es compatible, esto significa esperar a que el trabajador finalice normalmente.

Detener un subproceso de forma cooperativa con un CancellationToken

En código nuevo, detenga un subproceso de forma cooperativa en lugar de forzarlo. El propietario del subproceso crea un CancellationTokenSource y pasa su Token al trabajador. El trabajador comprueba el token en puntos seguros entre unidades de trabajo y, cuando ve una solicitud de cancelación, deja de iniciar trabajo nuevo, libera los recursos que posee y regresa. El propietario solicita la detención con Cancel() y después llama a Join() para esperar hasta que el trabajador haya terminado de verdad. La cancelación es solo una solicitud: un trabajador que nunca comprueba el token, o que está bloqueado en una sola llamada bloqueante larga, no se detiene hasta que observa el token, así que mantenga cada paso corto o use API que acepten el token directamente.

Llamar a token.ThrowIfCancellationRequested() lanza una OperationCanceledException cuando se ha solicitado la cancelación, lo que permite ejecutar la limpieza en un bloque catch o finally antes de que el subproceso regrese:

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.

Esta salida se registró en .NET 10. El número exacto de pasos depende del tiempo, pero el orden no: el trabajador informa primero de su limpieza, y la línea Main: worker has stopped. solo puede aparecer después de que Join() regrese, así que la detención queda confirmada en lugar de supuesta.

Errores comunes al detener un subproceso

  • Usar Abort() en código nuevo o en un entorno de ejecución moderno. Thread.Abort() solo se admite en .NET Framework. En .NET Core y .NET 5 o posterior lanza una PlatformNotSupportedException (el compilador también advierte SYSLIB0006), por lo que el trabajador no se detiene en absoluto y simplemente se ejecuta hasta el final, como muestra el primer ejemplo en .NET 10. El código que debe ejecutarse en un entorno moderno no tiene un recurso de Abort() que funcione y necesita cancelación cooperativa en su lugar.
  • Solicitar la cancelación pero nunca comprobar el token. Cancel() solo establece una marca. Si el bucle del trabajador nunca llama a ThrowIfCancellationRequested() y nunca lee IsCancellationRequested, no ocurre nada y el subproceso sigue ejecutándose.
  • Tratar la solicitud de detención como prueba de que el subproceso terminó. Ni Cancel() ni Abort() esperan al trabajador. Llame siempre a Join() (u observe la finalización del trabajador de otra manera) antes de usar sus resultados o liberar objetos que todavía toca.
  • Interrumpir a un trabajador en mitad de una actualización. En los entornos donde Abort() funciona, la excepción asíncrona puede llegar mientras el trabajador mantiene un bloqueo o ha actualizado datos compartidos a la mitad, dejando el estado incoherente para los demás subprocesos. La cancelación cooperativa lo evita porque el propio trabajador elige el punto seguro en el que detenerse.