Una guía del programador de .NET para CancellationToken

[ad_1]

A veces la resignación es algo bueno. En muchos de mis proyectos .NET, he tenido una amplia motivación para acabar con los procesos internos y externos. Microsoft se enteró de que los desarrolladores estaban abordando este caso de uso común en una variedad de implementaciones complejas y decidió que tenía que haber una mejor manera. Por lo tanto, se introdujo un patrón de comunicación de aborto común CancellationToken, que se creó utilizando construcciones de comunicación entre procesos y subprocesos múltiples de nivel inferior. Como parte de mi investigación inicial sobre este patrón, y después de revisar el código fuente real de .NET para la implementación de Microsoft, descubrí esto CancellationToken puede resolver una gama mucho más amplia de problemas: suscripciones al estado de ejecución de las aplicaciones, operaciones de tiempo de espera con varios disparadores y comunicación general entre procesos a través de banderas.

Índice
  1. El caso de uso previsto de CancellationToken
  2. Token de cancelación bajo un microscopio
  3. CancellationTokens para eventos extendidos
  4. CancellationToken para el tiempo de espera
  5. CancellationToken como mecanismo de notificación
  6. Lectura adicional en el blog de ingeniería de Toptal:

El caso de uso previsto de CancellationToken

CancellationToken se introdujo en .NET 4 para mejorar y estandarizar las soluciones existentes para cancelar operaciones. Hay cuatro enfoques generales para manejar las cancelaciones que los lenguajes de programación populares tienden a implementar:

MatarDile, no aceptes un no por respuestaPregunte cortésmente y acepte un rechazoLevanta la bandera cortésmente, haz que te pregunten si quieres
AcercarseParada fuerte; arreglar las discrepancias más tardeDile que se detenga, pero deja que limpie las cosas.Una solicitud directa pero gentil para detenerPídeles que se detengan, pero no los obligues.
resumenUn camino seguro hacia la corrupción y el dolorPermite puntos de parada limpios, pero debe detenersePermite puntos de interrupción limpios, pero se puede ignorar la solicitud de cancelaciónLa cancelación se solicita a través de una bandera.
subprocesospthread_kill,
pthread_cancel (asincrónico)
pthread_cancel (modo diferido)n / Apor una bandera
.LA REDThread.Abortn / AThread.InterruptA través de una bandera CancellationToken
JavaThread.destroy,
Thread.stop
n / AThread.interruptcon una bandera o Thread.interrupted
pitónPyThreadState_SetAsyncExcn / Aasyncio.Task.cancelpor una bandera
guíaInaceptable; evitar esta prácticaAceptable, especialmente si un idioma no admite excepciones o manejoAceptable si el idioma lo admiteMejor, pero más de un trabajo en grupo
Resumen del enfoque de cancelación y ejemplos de lenguaje

CancellationToken está en la última categoría donde la conversación anular es cooperativa.

Introducido después de Microsoft CancellationToken, la comunidad de desarrolladores lo adoptó rápidamente, especialmente porque muchas API clave de .NET se actualizaron para usar estos tokens de forma nativa. Por ejemplo, las acciones que comienzan con ASP.NET Core 2.0 admiten una opción CancellationToken Parámetro que puede señalar si una solicitud HTTP ha sido cerrada, permitiendo abortar cualquier operación y evitando así el uso innecesario de recursos.

Después de una inmersión profunda en el código base de .NET, quedó claro CancellationTokenEl uso de no se limita a la cancelación.

Token de cancelación bajo un microscopio

En consideración adicional CancellationTokenvemos que es solo una bandera simple (es decir, ManualResetEvent) y la infraestructura de soporte que brinda la capacidad de monitorear y cambiar esta bandera. CancellationTokenLa principal utilidad de está contenida en su nombre, lo que sugiere que es el método habitual de cancelación de operaciones. Hoy en día, cualquier biblioteca, paquete o marco .NET con operaciones asincrónicas o de ejecución prolongada permite la cancelación a través de estos tokens.

CancellationToken se puede activar configurando manualmente su indicador en verdadero o programándolo para que se vuelva verdadero después de que haya transcurrido una cierta cantidad de tiempo. No importa cómo un CancellationToken se activa, el código de cliente que supervisa ese token puede determinar el valor del indicador del token mediante uno de estos tres métodos:

  • usando un WaitHandle
  • consulta de CancellationTokenla bandera de
  • Informar al código del cliente cuando se actualiza el estado de la bandera a través de una suscripción programática

Después de investigar más a fondo el código base de .NET, resultó que el equipo de .NET lo había encontrado CancellationTokens útil en otros escenarios no relacionados con la cancelación. Veamos algunos de estos casos de uso avanzados y fuera de marca que brindan a los desarrolladores de C# coordinación entre procesos y subprocesos múltiples para simplificar situaciones complejas.

CancellationTokens para eventos extendidos

Al escribir aplicaciones ASP.NET Core, a veces necesitamos saber cuándo se inició nuestra aplicación o necesitamos inyectar nuestro código en el proceso de apagado del host. En estos casos utilizamos el IHostApplicationLifetime Interfaz (anteriormente IApplicationLifetime). Esta interfaz (del repositorio de .NET Core) utiliza CancellationToken para comunicar tres eventos principales: ApplicationStarted, ApplicationStoppingy ApplicationStopped:

namespace Microsoft.Extensions.Hosting
{
    /// <summary>
    /// Allows consumers to be notified of application lifetime events. 
    /// This interface is not intended to be user-replaceable.
    /// </summary>
    public interface IHostApplicationLifetime
    {
        /// <summary>
        /// Triggered when the application host has fully started.
        /// </summary>
        CancellationToken ApplicationStarted { get; }

        /// <summary>
        /// Triggered when the application host is starting a graceful shutdown.
        /// Shutdown will block until all callbacks registered on 
        /// this token have completed.
        /// </summary>
        CancellationToken ApplicationStopping { get; }

        /// <summary>
        /// Triggered when the application host has completed a graceful shutdown.
        /// The application will not exit until all callbacks registered on 
        /// this token have completed.
        /// </summary>
        CancellationToken ApplicationStopped { get; }

        /// <summary>
        /// Requests termination of the current application.
        /// </summary>
        void StopApplication();
    }
}

A primera vista puede verse así CancellationTokens no pertenecen aquí, sobre todo porque se utilizan como eventos. Sin embargo, investigaciones posteriores muestran que estos tokens son una combinación perfecta:

  • Son flexibles y brindan al cliente de la interfaz múltiples formas de escuchar estos eventos.
  • Son seguros para subprocesos fuera de la caja.
  • Se pueden crear mediante la combinación de diferentes fuentes. CancellationTokens.

A pesar de CancellationTokenLos correos electrónicos no son perfectos para todas las necesidades de eventos, son ideales para eventos que solo ocurren una vez, como iniciar o detener una aplicación.

CancellationToken para el tiempo de espera

Por defecto, ASP.NET nos da muy poco tiempo para cerrar. En aquellos casos en los que necesitemos un poco más de tiempo utilizar el incorporado HostOptions clase podemos cambiar este valor de tiempo de espera. Debajo de este valor de tiempo de espera está encerrado en un CancellationToken y se alimenta a los subprocesos subyacentes.

IHostedService's StopAsync El método es un gran ejemplo de este uso:

namespace Microsoft.Extensions.Hosting
{
    /// <summary>
    /// Defines methods for objects that are managed by the host.
    /// </summary>
    public interface IHostedService
    {
        /// <summary>
        /// Triggered when the application host is ready to start the service.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the start
        ///     process has been aborted.</param>
        Task StartAsync(CancellationToken cancellationToken);

        /// <summary>
        /// Triggered when the application host is performing a graceful shutdown.
        /// </summary>
        /// <param name="cancellationToken">Indicates that the shutdown 
        ///     process should no longer be graceful.</param>
        Task StopAsync(CancellationToken cancellationToken);
    }
}

Como puede verse en el IHostedService Definición de interfaz, la StopAsync se necesita método CancellationToken Parámetro. El comentario asociado con este parámetro comunica claramente la intención original de Microsoft de CancellationToken era un mecanismo de tiempo de espera en lugar de un proceso de aborto.

En mi opinión, si esta interfaz hubiera existido antes CancellationToken's existencia, esto podría haber sido uno TimeSpan Parámetros: para especificar cuánto tiempo se permitió que se ejecutara la operación de detención. En mi experiencia, los escenarios de tiempo de espera casi siempre se pueden convertir en un CancellationToken con gran valor añadido.

Olvidemos por el momento que sabemos cómo hacer esto. StopAsync está diseñado el método y, en su lugar, pensar en cómo diseñaríamos el contrato de ese método. Primero, definamos los requisitos:

  • Que StopAsync El método debe intentar detener el servicio.
  • Que StopAsync El método debe tener un estado de parada adecuado.
  • Independientemente de si se alcanza un estado de detención correcto, un servicio alojado debe tener un tiempo máximo para detenerse, según lo define nuestro parámetro de tiempo de espera.

al obtener un StopAsync método en cualquier forma cumplimos con el primer requisito. El resto de los requisitos son complicados. CancellationToken satisface exactamente estas necesidades mediante el uso de una herramienta de comunicación estándar basada en banderas de .NET para fortalecer la conversación.

CancellationToken como mecanismo de notificación

El mayor secreto detrás de esto. CancellationToken es que es solo una bandera. Ilustremos cómo CancellationToken se puede utilizar para iniciar procesos en lugar de detenerlos.

Considera lo siguiente:

  1. Un... crear RandomWorker Clase.
  2. RandomWorker debería tener uno DoWorkAsync Método que realiza trabajo aleatorio.
  3. Que DoWorkAsync El método debe permitir que la persona que llama indique cuándo debe comenzar el trabajo.
public class RandomWorker
{
    public RandomWorker(int id)
    {
        Id = id;
    }

    public int Id { get; }

    public async Task DoWorkAsync()
    {
        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[Worker {Id}] Iteration {i}");
            await Task.Delay(1000);
        }
    }
}

La clase anterior satisface los dos primeros requisitos, dejándonos con el tercero. Hay varias interfaces alternativas que podríamos usar para activar nuestro trabajador, como un lapso de tiempo o una bandera simple:

# With a time span
Task DoWorkAsync(TimeSpan startAfter);

# Or a simple flag
bool ShouldStart { get; set; }
Task DoWorkAsync();

Ambos enfoques están bien, pero nada es tan elegante como usar un CancellationToken:

public class RandomWorker
{
    public RandomWorker(int id)
    {
        Id = id;
    }

    public int Id { get; }

    public async Task DoWorkAsync(CancellationToken startToken)
    {
        startToken.WaitHandle.WaitOne();

        for (int i = 1; i <= 10; i++)
        {
            Console.WriteLine($"[Worker {Id}] Iteration {i}");
            await Task.Delay(1000);
        }
    }
}

Este código de cliente de muestra demuestra el poder de este tema:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CancelToStart
{
    public class Program
    {
        static void Main(string[] args)
        {
            CancellationTokenSource startCts = new CancellationTokenSource();

            startCts.CancelAfter(TimeSpan.FromSeconds(10));

            var tasks = Enumerable.Range(0, 10)
                .Select(i => new RandomWorker(i))
                .Select(worker => worker.DoWorkAsync(startCts.Token))
                .ToArray();

            Task.WaitAll(tasks, CancellationToken.None);
        }
    }
}

Que CancellationTokenSource creará el nuestro CancellationToken entre bastidores y coordinar la activación de todos los procesos asociados. En este caso, el procedimiento relacionado es nuestro. RandomWorker, esperando para empezar. Este enfoque nos permite aprovechar la seguridad de subprocesos integrada en el valor predeterminado CancellationToken Implementación.

Estos ejemplos muestran cómo CancellationToken proporciona una caja de herramientas de soluciones que son útiles fuera del caso de uso previsto. Las herramientas pueden ser útiles en muchos escenarios que involucran comunicación entre procesos basada en banderas. Ya sea que nos enfrentemos a tiempos de espera, notificaciones o eventos únicos, podemos recurrir a esta elegante implementación probada por Microsoft.

Palabras de arriba a abajo
Como Microsoft Gold Partner, Toptal es su red élite de expertos de Microsoft. ¡Cree equipos de alto rendimiento con los expertos que necesita, en cualquier lugar, cuando los necesite!

Lectura adicional en el blog de ingeniería de Toptal:

[ad_2]

Si quieres conocer otros artículos parecidos a Una guía del programador de .NET para CancellationToken puedes visitar la categoría Software.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir