Come utilizzare la proprietà CancellationToken?


117

Rispetto al codice precedente per la classe RulyCanceler , volevo eseguire il codice utilizzando CancellationTokenSource.

Come lo uso come menzionato in Token di cancellazione , cioè senza lanciare / catturare un'eccezione? Posso usare la IsCancellationRequestedproprietà?

Ho provato a usarlo in questo modo:

cancelToken.ThrowIfCancellationRequested();

e

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

ma questo ha dato un errore di runtime cancelToken.ThrowIfCancellationRequested();nel metodo 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:

Il codice che ho eseguito con successo ha catturato OperationCanceledException nel nuovo 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();
      }
    }
  }
}

2
docs.microsoft.com/en-us/dotnet/standard/threading/… ha alcuni buoni esempi di utilizzo CancellationTokenSourcecon metodi asincroni, metodi a esecuzione prolungata con polling e utilizzo di una richiamata.
Ehtesh Choudhury

Questo articolo mostra le opzioni che hai e di cui hai bisogno per gestire il token in base al tuo caso specifico.
Ognyan Dimitrov

Risposte:


140

Puoi implementare il tuo metodo di lavoro come segue:

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

Questo è tutto. Devi sempre gestire la cancellazione da solo - esci dal metodo quando è il momento opportuno per uscire (in modo che il tuo lavoro ei tuoi dati siano in uno stato coerente)

AGGIORNAMENTO: preferisco non scrivere while (!cancelToken.IsCancellationRequested)perché spesso ci sono pochi punti di uscita in cui è possibile interrompere l'esecuzione in modo sicuro attraverso il corpo del ciclo e il ciclo di solito ha una condizione logica per uscire (iterare su tutti gli elementi nella raccolta ecc.). Quindi credo che sia meglio non mescolare queste condizioni perché hanno intenzioni diverse.

Nota cautelativa su come evitare CancellationToken.ThrowIfCancellationRequested():

Commento in questione di Eamon Nerbonne :

... sostituendo ThrowIfCancellationRequestedcon un mucchio di controlli per le IsCancellationRequesteduscite con garbo, come dice questa risposta. Ma non è solo un dettaglio di implementazione; che influisce sul comportamento osservabile: l'attività non terminerà più nello stato annullato, ma in RanToCompletion. E questo può influenzare non solo i controlli di stato espliciti, ma anche, in modo più sottile, il concatenamento di attività con ContinueWith, ad esempio , a seconda TaskContinuationOptionsdell'uso. Direi che evitare ThrowIfCancellationRequestedè un consiglio pericoloso.


1
Grazie! Questo non segue dal testo in linea, abbastanza autorevole (libro "C # 4.0 in a Nutshell"?) Che ho citato. Puoi darmi un riferimento su "sempre"?
Fulproof

1
Questo deriva dalla pratica e dall'esperienza =). Non ricordo da dove lo so. Ho usato "hai sempre bisogno" perché in realtà puoi interrompere il thread di lavoro con eccezioni dall'esterno usando Thread.Abort (), ma è una pessima pratica. A proposito, l'uso di CancellationToken.ThrowIfCancellationRequested () è anche "gestire la cancellazione da soli", solo l'altro modo per farlo.
Sasha

1
@ OleksandrPshenychnyy intendevo sostituire while (true) con while (! CancelToken.IsCancellationRequested). Questo è stato utile! Grazie!
Doug Dawson,

1
@Fulproof Non esiste un modo generico per un runtime di annullare il codice in esecuzione perché i runtime non sono abbastanza intelligenti da sapere dove un processo può essere interrotto. In alcuni casi è possibile uscire semplicemente da un ciclo, in altri casi è necessaria una logica più complessa, ovvero le transazioni devono essere annullate, le risorse devono essere rilasciate (ad esempio, gli handle di file o le connessioni di rete). Questo è il motivo per cui non esiste un modo magico per annullare un'attività senza dover scrivere del codice. Quello che pensi è come uccidere un processo, ma non è annullarlo, è una delle cose peggiori che possono accadere a un'applicazione perché non è possibile ripulire.
user3285954

1
@kosist È possibile utilizzare CancellationToken.None se non si prevede di annullare l'operazione che si sta avviando manualmente. Ovviamente quando il processo di sistema viene terminato, tutto viene interrotto e CancellationToken non ha nulla a che fare con questo. Quindi sì, dovresti creare CancellationTokenSource solo se hai davvero bisogno di usarlo per annullare l'operazione. Non ha senso creare qualcosa che non usi.
Sasha

26

@ BrainSlugs83

Non dovresti fidarti ciecamente di tutto ciò che viene pubblicato su stackoverflow. Il commento nel codice Jens non è corretto, il parametro non controlla se le eccezioni vengono lanciate o meno.

MSDN è molto chiaro cosa controlla quel parametro, l'hai letto? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Se throwOnFirstExceptionè vero, un'eccezione si propagherà immediatamente fuori dalla chiamata a Cancel, impedendo l'elaborazione delle restanti callback e operazioni cancellabili. Se throwOnFirstExceptionè false, questo overload aggregherà tutte le eccezioni generate in un AggregateException, in modo tale che un callback che genera un'eccezione non impedirà l'esecuzione di altri callback registrati.

Anche il nome della variabile è sbagliato perché Annulla viene chiamato CancellationTokenSourcenon sul token stesso e l'origine cambia lo stato di ogni token che gestisce.



1
Questa è un'informazione molto utile, ma non risponde affatto alla domanda posta.
11nallan11

16

Puoi creare un'attività con token di annullamento, quando app vai in background puoi annullare questo token.

Puoi farlo in 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);

Un'altra soluzione è il timer utente in Xamarin.Forms, interrompi il timer quando l'app passa in background https://xamarinhelp.com/xamarin-forms-timer/


10

È possibile utilizzare ThrowIfCancellationRequestedsenza manipolare l'eccezione!

L'uso di ThrowIfCancellationRequesteddeve essere utilizzato dall'interno di a Task(non a Thread). Quando viene utilizzato all'interno di a Task, non è necessario gestire l'eccezione da soli (e ottenere l'errore Eccezione non gestita). Risulterà in lasciare il Task, e la Task.IsCancelledproprietà sarà True. Non è necessaria la gestione delle eccezioni.

Nel tuo caso specifico, modifica Threadin a Task.

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

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}

Perché stai usando t.Start()e no Task.Run()?
Xander Luciano

1
@XanderLuciano: in questo esempio non c'è un motivo specifico e Task.Run () sarebbe stata la scelta migliore.
Titus

5

Devi passare il CancellationTokentoken all'attività, che monitorerà periodicamente il token per vedere se è richiesta la cancellazione.

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

In questo caso, l'operazione terminerà quando verrà richiesta la cancellazione e Taskavrà uno RanToCompletionstato. Se vuoi essere riconosciuto che la tua attività è stata annullata , devi usare ThrowIfCancellationRequestedper lanciare OperationCanceledExceptionun'eccezione.

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

Spero che questo aiuti a capire meglio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.