paint-brush
¿Por qué necesita un token de cancelación en C# para las tareas?por@igorlopushko
89,932 lecturas
89,932 lecturas

¿Por qué necesita un token de cancelación en C# para las tareas?

por Igor Lopushko2022/03/30
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Cuando ejecuta una tarea en C, puede llevar un tiempo ejecutarla. En algunos casos, le gustaría cancelar una operación tan larga. Un token de cancelación (https://://.ms.com/en-us/dotnet/api//system.threading.cancellating.tasks?view=net-6.0) permite la cancelación cooperativa entre subprocesos, el trabajo del grupo de subprocesos elementos o Tarea (http://www.msn.org/s/windows-windows-tasks) El algoritmo sigue un algoritmo para crear un objeto que indica la cancelación del token. Pase la propiedad `CancellatedTokenSource.Token` como un objeto token a la tarea.

Company Mentioned

Mention Thumbnail
featured image - ¿Por qué necesita un token de cancelación en C# para las tareas?
Igor Lopushko HackerNoon profile picture

Un CancellationToken permite la cancelación cooperativa entre subprocesos, elementos de trabajo del grupo de subprocesos u objetos Task . En este artículo, me gustaría discutir el mecanismo que se aplica a los objetos Task.


Cuando ejecuta una tarea en C#, puede llevar un tiempo ejecutarla. En algunos casos, le gustaría cancelar una operación tan larga. Puede haber varias razones: tiempo de espera de la operación, exceder los límites de recursos, etc.

el algoritmo

  1. Cree un objeto del tipo CancellationTokenSource que indique la cancelación del token.
  2. Pase la propiedad CancellationTokenSource.Token como un objeto token a la tarea.
  3. Defina el comportamiento de la tarea para terminar la operación de acuerdo con la señal de cancelación.
  4. Llame al método CancellationTokenSource.Cancel() que establece la propiedad CancellationToken.IsCancellationRequested en un valor true . Eso significa que el método Cancel() no cancela la operación en sí. Simplemente cambia el valor de la propiedad IsCancellationRequested . Nosotros, como desarrolladores, tenemos que definir la lógica de cancelación por nosotros mismos.


El tipo CancellationTokenSource implementa la interfaz IDisposable y debe liberarse cuando se completa una tarea. Se puede hacer manualmente llamando al método Dispose() o vi using la construcción.


Ejemplo de código para demostrar el algoritmo anterior:


 // initialize cancellation objects CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); CancellationToken token = cancelTokenSource.Token; // execute a parallel operation Task task = new Task(() => { some_operations }, token); task.Start(); // cancel the operation cancelTokenSource.Cancel(); // release resources cancelTokenSource.Dispose();


Discutamos el paso #3 en detalle. Hay dos formas de definir la lógica de finalización de la tarea mediante un token de cancelación:


  1. Utilice el operador de return para salir de la ejecución de la tarea. En este caso, el estado de la tarea será TaskStatus.RunToCompletion .
  2. Ejecute la excepción de tipo OperationCanceledException a través de la llamada al método ThrowIfCancellationRequested() . En este caso, el estado de la tarea será TaskStatus.Canceled .

Tarea completa a través del operador de return

 public static void Main(string[] args) { CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); CancellationToken token = cancelTokenSource.Token; Task task = new Task(() => { for (int i = 1; i < 100; i++) { if (token.IsCancellationRequested) { Console.WriteLine("Operation is canceled"); return; } Console.WriteLine($"Count is equal to '{i}'"); //add some timeout to emulate real-life execution Thread.Sleep(10); } }, token); task.Start(); // add some timeout to emulate real-life execution Thread.Sleep(100); // cancel the parallel operation cancelTokenSource.Cancel(); // wait till the operation is completed task.Wait(); // check the operation status Console.WriteLine($"Task Status is equal to '{ task.Status }'"); // release resources cancelTokenSource.Dispose(); }


El resultado de esta ejecución es el siguiente:

 Count is equal to '1' Count is equal to '2' Count is equal to '3' Count is equal to '4' Count is equal to '5' Operation is canceled Task Status is equal to 'RanToCompletion'

Tarea completa a través de la llamada al método ThrowIfCancellationRequested()

 public static void Main(string[] args) { CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); CancellationToken token = cancelTokenSource.Token; Task task = new Task(() => { for (int i = 1; i < 100; i++) { if (token.IsCancellationRequested) token.ThrowIfCancellationRequested(); Console.WriteLine($"Count is equal to '{i}'"); //add some timeout to emulate real-life execution Thread.Sleep(10); } }, token); try { task.Start(); // add some timeout to emulate real-life execution Thread.Sleep(100); // cancel the parallel operation cancelTokenSource.Cancel(); // wait till the operation is completed task.Wait(); } catch (AggregateException ae) { foreach (Exception e in ae.InnerExceptions) { if (e is TaskCanceledException) Console.WriteLine("Operation is canceled"); else Console.WriteLine(e.Message); } } finally { // release resources cancelTokenSource.Dispose(); } // check the operation status Console.WriteLine($"Task Status is equal to '{ task.Status }'"); }


El resultado de esta ejecución es el siguiente:


 Count is equal to '1' Count is equal to '2' Count is equal to '3' Count is equal to '4' Count is equal to '5' Operation is canceled Task Status is equal to 'Canceled'


La excepción lanzada aparecerá como una InnerException de AggregateException . Si la tarea se canceló a través de la llamada al método ThrowIfCancellationRequested() , la excepción será del tipo TaskCanceledException . El código verifica este tipo para un manejo adecuado; de lo contrario, maneje otro motivo de excepción.


La excepción se lanzará solo en caso de que se llame al método Wait() o WaitAll() para la tarea. De lo contrario, no se lanza ninguna excepción, solo se establece TaskStatus.Canceled .

Controlador de cancelación de operaciones de registro

Otra forma de definir la lógica de la cancelación de la tarea es usar el método Register() . Registra un delegado de acción que se llamará cuando se cancele CancellationToken .


 public static void Main(string[] args) { CancellationTokenSource cancelTokenSource = new CancellationTokenSource(); CancellationToken token = cancelTokenSource.Token; Task task = new Task(() => { int i = 1; token.Register(() => { Console.WriteLine("Operation is canceled"); i = 100; Console.WriteLine($"Count is equal to '{i}'"); }); for (; i < 100; i++) { Console.WriteLine($"Count is equal to '{i}'"); //add some timeout to emulate real-life execution Thread.Sleep(10); } }, token); task.Start(); // add some timeout to emulate real-life execution Thread.Sleep(100); // cancel the parallel operation cancelTokenSource.Cancel(); // wait till the operation is completed task.Wait(); // check the operation status Console.WriteLine($"Task Status is equal to '{ task.Status }'"); // release resources cancelTokenSource.Dispose(); }


El resultado de esta ejecución es el siguiente:

 Count is equal to '1' Count is equal to '2' Count is equal to '3' Count is equal to '4' Count is equal to '5' Operation is canceled Count is equal to '100' Task Status is equal to 'RanToCompletion'


En este código, el método cancelTokenSource.Cancel() se llama y se activa el delegado definido en el método token.Register() . En este ejemplo, el código establece la variable i en el valor 100 , lo que provoca el final de la ejecución de la tarea.


Si el código no espera la competencia de la operación, el estado de la tarea será TaskStatus.Running . Si se llama al método Wait() o WaitAll() , el estado de la tarea será TaskStatus.RanToCompletion .

Resumen: uso de un token de cancelación

La cancelación de la tarea es muy importante para optimizar la lógica de su aplicación. Es posible que deba cancelar la tarea por muchas razones: tiempo de espera de la operación, exceso de los límites de recursos, etc. Siempre debe manejar la lógica de cancelación usted mismo. Puede hacerlo mediante el operador de return o mediante la llamada al método ThrowIfCancellationRequested() .