Comment utiliser la propriété CancellationToken?

118

Par rapport au code précédent pour la classe RulyCanceler , je voulais exécuter du code en utilisant CancellationTokenSource.

Comment puis-je l'utiliser comme mentionné dans les jetons d'annulation , c'est-à-dire sans lancer / attraper une exception? Puis-je utiliser la IsCancellationRequestedpropriété?

J'ai essayé de l'utiliser comme ceci:

cancelToken.ThrowIfCancellationRequested();

et

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

mais cela a donné une erreur d'exécution sur la cancelToken.ThrowIfCancellationRequested();méthode 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:

Le code que j'ai exécuté avec succès a attrapé l'exception OperationCanceledException dans le nouveau thread:

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
la source
2
docs.microsoft.com/en-us/dotnet/standard/threading/… a de très bons exemples d'utilisation CancellationTokenSourceavec des méthodes asynchrones, des méthodes de longue durée avec interrogation et l'utilisation d'un rappel.
Ehtesh Choudhury
Cet article présente les options dont vous disposez et dont vous avez besoin pour gérer le jeton en fonction de votre cas spécifique.
Ognyan Dimitrov

Réponses:

140

Vous pouvez implémenter votre méthode de travail comme suit:

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

C'est tout. Vous devez toujours gérer l'annulation par vous-même - quitter la méthode au moment opportun pour quitter (afin que votre travail et vos données soient dans un état cohérent)

MISE À JOUR: Je préfère ne pas écrire while (!cancelToken.IsCancellationRequested)car il y a souvent peu de points de sortie où vous pouvez arrêter de s'exécuter en toute sécurité sur le corps de la boucle, et la boucle a généralement une condition logique pour quitter (itérer sur tous les éléments de la collection, etc.). Je pense donc qu'il vaut mieux ne pas mélanger ces conditions car elles ont une intention différente.

Attention à éviter CancellationToken.ThrowIfCancellationRequested():

Commentaire en question par Eamon Nerbonne :

... remplaçant gracieusement ThrowIfCancellationRequestedpar un tas de vérifications pour les IsCancellationRequestedsorties, comme le dit cette réponse. Mais ce n'est pas seulement un détail de mise en œuvre; qui affecte le comportement observable: la tâche ne se terminera plus dans l'état annulé, mais dans RanToCompletion. Et cela peut affecter non seulement les vérifications d'état explicites, mais aussi, plus subtilement, l'enchaînement des tâches avec par exemple ContinueWith, en fonction de l' TaskContinuationOptionsutilisation. Je dirais qu'éviter ThrowIfCancellationRequestedest un conseil dangereux.

Sasha
la source
1
Merci! Cela ne découle pas du texte en ligne, tout à fait faisant autorité (livre "C # 4.0 en bref"?) Que j'ai cité. Pourriez-vous me donner une référence sur «toujours»?
Fulproof
1
Cela vient de la pratique et de l'expérience =). Je ne me souviens pas d'où je sais cela. J'ai utilisé "vous avez toujours besoin" parce que vous pouvez en fait interrompre le thread de travail à l'exception de l'extérieur en utilisant Thread.Abort (), mais c'est une très mauvaise pratique. En passant, utiliser CancellationToken.ThrowIfCancellationRequested () est également "gérer l'annulation par vous-même", juste l'autre façon de le faire.
Sasha
1
@OleksandrPshenychnyy Je voulais dire remplacer while (true) par while (! CancelToken.IsCancellationRequested). Cela a été utile! Merci!
Doug Dawson
1
@Fulproof Il n'y a pas de moyen générique pour un runtime d'annuler le code en cours d'exécution car les runtimes ne sont pas assez intelligents pour savoir où un processus peut être interrompu. Dans certains cas, il est possible de simplement quitter une boucle, dans d'autres cas, une logique plus complexe est nécessaire, c'est-à-dire que les transactions doivent être annulées, des ressources doivent être libérées (par exemple, des descripteurs de fichiers ou des connexions réseau). C'est pourquoi il n'y a pas de moyen magique d'annuler une tâche sans avoir à écrire du code. Ce à quoi vous pensez, c'est comme tuer un processus, mais ce n'est pas annuler, c'est l'une des pires choses qui puisse arriver à une application car elle ne peut pas nettoyer.
user3285954
1
@kosist Vous pouvez utiliser CancellationToken.None si vous ne prévoyez pas d'annuler l'opération que vous démarrez manuellement. Bien sûr, lorsque le processus système est tué, tout est interrompu et CancellationToken n'a rien à voir avec cela. Donc oui, vous ne devriez créer CancellationTokenSource que si vous avez vraiment besoin de l'utiliser pour annuler l'opération. Il n'y a aucun sens à créer quelque chose que vous n'utilisez pas.
Sasha
26

@ BrainSlugs83

Vous ne devriez pas faire confiance aveuglément à tout ce qui est publié sur stackoverflow. Le commentaire dans le code Jens est incorrect, le paramètre ne contrôle pas si les exceptions sont levées ou non.

MSDN est très clair ce que contrôle ce paramètre, l'avez-vous lu? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Si la valeur throwOnFirstExceptionest true, une exception se propage immédiatement hors de l'appel à Cancel, empêchant le traitement des rappels restants et des opérations annulables. Si throwOnFirstExceptionest false, cette surcharge agrégera toutes les exceptions levées dans un AggregateException, de sorte qu'un rappel lançant une exception n'empêchera pas l'exécution d'autres rappels enregistrés.

Le nom de la variable est également erroné car Cancel n'est pas appelé sur CancellationTokenSourcele jeton lui-même et la source change l'état de chaque jeton qu'elle gère.

user3285954
la source
Jetez également un œil à la documentation (TAP) ici sur l'utilisation proposée du jeton d'annulation: docs.microsoft.com/en-us/dotnet/standard
...
1
C'est une information très utile, mais elle ne répond pas du tout à la question posée.
11nallan11
16

Vous pouvez créer une tâche avec un jeton d'annulation, lorsque vous passez en arrière-plan, vous pouvez annuler ce jeton.

Vous pouvez le faire dans 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);

Une autre solution est le minuteur utilisateur dans Xamarin.Forms, arrêtez le minuteur lorsque l'application est en arrière-plan https://xamarinhelp.com/xamarin-forms-timer/

Jesse Jiang
la source
10

Vous pouvez utiliser ThrowIfCancellationRequestedsans gérer l'exception!

L'utilisation de ThrowIfCancellationRequestedest destinée à être utilisée à partir d'un Task(et non d'un Thread). Lorsqu'il est utilisé dans a Task, vous ne devez pas gérer l'exception vous-même (et obtenir l'erreur Exception non gérée). Cela entraînera la sortie de Task, et la Task.IsCancelledpropriété sera True. Aucune gestion d'exception requise.

Dans votre cas spécifique, remplacez le Threadpar un Task.

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

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}
Titus
la source
Pourquoi utilisez-vous t.Start()et non Task.Run()?
Xander Luciano
1
@XanderLuciano: Dans cet exemple, il n'y a pas de raison spécifique, et Task.Run () aurait été le meilleur choix.
Titus
5

Vous devez transmettre le CancellationTokenà la tâche, qui surveillera périodiquement le jeton pour voir si l'annulation est demandée.

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(); 

Dans ce cas, l'opération se terminera lorsque l'annulation sera demandée et le Taskaura un RanToCompletionétat. Si vous voulez être reconnu que votre tâche a été annulée , vous devez utiliser ThrowIfCancellationRequestedpour lever une OperationCanceledExceptionexception.

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(); 

J'espère que cela aide à mieux comprendre.

Mahbubur Rahman
la source