¿Cómo utilizar la propiedad CancellationToken?

117

En comparación con el código anterior para la clase RulyCanceler , quería ejecutar código usando CancellationTokenSource.

¿Cómo lo uso como se menciona en los tokens de cancelación , es decir, sin lanzar / capturar una excepción? ¿Puedo usar la IsCancellationRequestedpropiedad?

Intenté usarlo así:

cancelToken.ThrowIfCancellationRequested();

y

try
{
  new Thread(() => Work(cancelSource.Token)).Start();
}
catch (OperationCanceledException)
{
  Console.WriteLine("Canceled!");
}

pero esto dio un error en tiempo de ejecución cancelToken.ThrowIfCancellationRequested();en el método Work(CancellationToken cancelToken):

System.OperationCanceledException was unhandled
  Message=The operation was canceled.
  Source=mscorlib
  StackTrace:
       at System.Threading.CancellationToken.ThrowIfCancellationRequested()
       at _7CancellationTokens.Token.Work(CancellationToken cancelToken) in C:\xxx\Token.cs:line 33
       at _7CancellationTokens.Token.<>c__DisplayClass1.<Main>b__0() in C:\xxx\Token.cs:line 22
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException:

El código que ejecuté con éxito captó la OperationCanceledException en el nuevo hilo:

using System;
using System.Threading;
namespace _7CancellationTokens
{
  internal class Token
  {
    private static void Main()
    {
      var cancelSource = new CancellationTokenSource();
      new Thread(() =>
      {
         try
         {
           Work(cancelSource.Token); //).Start();
         }
         catch (OperationCanceledException)
         {
            Console.WriteLine("Canceled!");
         }
         }).Start();

      Thread.Sleep(1000);
      cancelSource.Cancel(); // Safely cancel worker.
      Console.ReadLine();
    }
    private static void Work(CancellationToken cancelToken)
    {
      while (true)
      {
        Console.Write("345");
        cancelToken.ThrowIfCancellationRequested();
      }
    }
  }
}
Fulproof
fuente
2
docs.microsoft.com/en-us/dotnet/standard/threading/… tiene algunos ejemplos bastante buenos de uso CancellationTokenSourcecon métodos asíncronos, métodos de ejecución prolongada con sondeo y uso de una devolución de llamada.
Ehtesh Choudhury
Este artículo muestra las opciones que tiene y necesita para manejar el token según su caso específico.
Ognyan Dimitrov

Respuestas:

140

Puede implementar su método de trabajo de la siguiente manera:

private static void Work(CancellationToken cancelToken)
{
    while (true)
    {
        if(cancelToken.IsCancellationRequested)
        {
            return;
        }
        Console.Write("345");
    }
}

Eso es. Siempre debe manejar la cancelación usted mismo: salga del método cuando sea el momento adecuado para salir (para que su trabajo y datos estén en un estado consistente)

ACTUALIZACIÓN: Prefiero no escribir while (!cancelToken.IsCancellationRequested)porque a menudo hay pocos puntos de salida donde puede dejar de ejecutar de forma segura en el cuerpo del bucle, y el bucle generalmente tiene alguna condición lógica para salir (iterar sobre todos los elementos de la colección, etc.). Así que creo que es mejor no mezclar esas condiciones, ya que tienen una intención diferente.

Nota de advertencia sobre cómo evitar CancellationToken.ThrowIfCancellationRequested():

Comentario en cuestión de Eamon Nerbonne :

... reemplazando ThrowIfCancellationRequestedcon un montón de controles de IsCancellationRequestedsalidas con gracia, como dice esta respuesta. Pero eso no es solo un detalle de implementación; que afecta el comportamiento observable: la tarea ya no terminará en el estado cancelado, sino en RanToCompletion. Y eso puede afectar no solo a las comprobaciones de estado explícitas, sino también, de manera más sutil, al encadenamiento de tareas con ContinueWith, por ejemplo , según el TaskContinuationOptionsutilizado. Yo diría que evitar ThrowIfCancellationRequestedes un consejo peligroso.

Sasha
fuente
1
¡Gracias! Esto no se sigue del texto en línea, bastante autorizado (¿libro "C # 4.0 en pocas palabras"?) Que cité. ¿Podría darme una referencia sobre "siempre"?
Fulproof
1
Esto proviene de la práctica y la experiencia =). No recuerdo de dónde lo sé. Usé "siempre necesitas" porque en realidad puedes interrumpir el hilo de trabajo con excepción desde afuera usando Thread.Abort (), pero esa es una muy mala práctica. Por cierto, usar CancellationToken.ThrowIfCancellationRequested () también es "manejar la cancelación usted mismo", solo la otra forma de hacerlo.
Sasha
1
@OleksandrPshenychnyy Me refiero a reemplazar while (verdadero) con while (! CancelToken.IsCancellationRequested). ¡Esto fue útil! ¡Gracias!
Doug Dawson
1
@Fulproof No existe una forma genérica de que un tiempo de ejecución cancele el código en ejecución porque los tiempos de ejecución no son lo suficientemente inteligentes como para saber dónde se puede interrumpir un proceso. En algunos casos es posible simplemente salir de un bucle, en otros casos se necesita una lógica más compleja, es decir, las transacciones deben revertirse, los recursos deben liberarse (por ejemplo, identificadores de archivos o conexiones de red). Es por eso que no existe una forma mágica de cancelar una tarea sin tener que escribir algún código. Lo que piensas es como matar un proceso, pero eso no es cancelar, es una de las peores cosas que le pueden pasar a una aplicación porque no se puede limpiar.
user3285954
1
@kosist Puede usar CancellationToken.None si no planea cancelar la operación que está iniciando manualmente. Por supuesto, cuando se mata el proceso del sistema, todo se interrumpe y CancellationToken no tiene nada que ver con eso. Entonces, sí, solo debe crear CancellationTokenSource si realmente necesita usarlo para cancelar la operación. No tiene sentido crear algo que no usas.
Sasha
26

@ BrainSlugs83

No debe confiar ciegamente en todo lo publicado en stackoverflow. El comentario en el código de Jens es incorrecto, el parámetro no controla si se lanzan excepciones o no.

MSDN tiene muy claro lo que controla ese parámetro, ¿lo ha leído? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Si throwOnFirstExceptiones cierto, una excepción se propagará inmediatamente fuera de la llamada a Cancelar, evitando que se procesen las devoluciones de llamada restantes y las operaciones cancelables. Si throwOnFirstExceptiones falso, esta sobrecarga agregará cualquier excepción lanzada en un AggregateException, de modo que una devolución de llamada que arroje una excepción no evitará que se ejecuten otras devoluciones de llamada registradas.

El nombre de la variable también es incorrecto porque se llama a Cancel, CancellationTokenSourceno al token en sí, y la fuente cambia el estado de cada token que administra.

user3285954
fuente
También eche un vistazo a la documentación (TAP) aquí sobre el uso propuesto del token de cancelación: docs.microsoft.com/en-us/dotnet/standard/…
Epstone
1
Esta es una información muy útil, pero no responde en absoluto a la pregunta formulada.
11nallan11
16

Puede crear una tarea con token de cancelación, cuando la aplicación vaya al fondo, puede cancelar este token.

Puede hacer esto en PCL https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/app-lifecycle

var cancelToken = new CancellationTokenSource();
Task.Factory.StartNew(async () => {
    await Task.Delay(10000);
    // call web API
}, cancelToken.Token);

//this stops the Task:
cancelToken.Cancel(false);

Otra solución es el temporizador de usuario en Xamarin.Forms, detener el temporizador cuando la aplicación vaya al fondo https://xamarinhelp.com/xamarin-forms-timer/

Jesse Jiang
fuente
10

Usted puede utilizar ThrowIfCancellationRequestedsin manipular la excepción!

El uso de ThrowIfCancellationRequestedestá destinado a usarse desde dentro de a Task(no a Thread). Cuando se usa dentro de a Task, no tiene que manejar la excepción usted mismo (y obtiene el error de Excepción no controlada). Dará como resultado dejar el Task, y la Task.IsCancelledpropiedad será True. No se necesita manejo de excepciones.

En su caso específico, cambie Threada a Task.

Task t = null;
try
{
    t = Task.Run(() => Work(cancelSource.Token), cancelSource.Token);
}

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}
Titus
fuente
¿Por qué estás usando t.Start()y no Task.Run()?
Xander Luciano
1
@XanderLuciano: En este ejemplo no hay una razón específica, y Task.Run () habría sido la mejor opción.
Titus
5

Debe pasar el CancellationTokena la tarea, que monitoreará periódicamente el token para ver si se solicita la cancelación.

CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
CancellationToken token = cancellationTokenSource.Token;  
Task task = Task.Run(() => {     
  while(!token.IsCancellationRequested) {
      Console.Write("*");         
      Thread.Sleep(1000);
  }
}, token);
Console.WriteLine("Press enter to stop the task"); 
Console.ReadLine(); 
cancellationTokenSource.Cancel(); 

En este caso, la operación finalizará cuando se solicite la cancelación y Tasktendrá un RanToCompletionestado. Si desea que se le reconozca que su tarea ha sido cancelada , debe usar ThrowIfCancellationRequestedpara lanzar una OperationCanceledExceptionexcepción.

Task task = Task.Run(() =>             
{                 
    while (!token.IsCancellationRequested) {
         Console.Write("*");                      
        Thread.Sleep(1000);                 
    }           
    token.ThrowIfCancellationRequested();               
}, token)
.ContinueWith(t =>
 {
      t.Exception?.Handle(e => true);
      Console.WriteLine("You have canceled the task");
 },TaskContinuationOptions.OnlyOnCanceled);  

Console.WriteLine("Press enter to stop the task");                 
Console.ReadLine();                 
cancellationTokenSource.Cancel();                 
task.Wait(); 

Espero que esto ayude a comprender mejor.

Mahbubur Rahman
fuente