Jak używać właściwości CancellationToken?

118

W porównaniu z poprzednim kodem dla klasy RulyCanceler , chciałem uruchomić kod przy użyciu CancellationTokenSource.

Jak go używać, jak wspomniano w tokenach anulowania , tj. Bez rzucania / łapania wyjątku? Czy mogę korzystać z IsCancellationRequestednieruchomości?

Próbowałem tego użyć w ten sposób:

cancelToken.ThrowIfCancellationRequested();

i

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

ale to spowodowało błąd cancelToken.ThrowIfCancellationRequested();w czasie wykonywania w metodzie 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:

Kod, który pomyślnie uruchomiłem, przechwycił OperationCanceledException w nowym wątku:

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();
      }
    }
  }
}
W pełni odporny
źródło
2
Docs.microsoft.com/en-us/dotnet/standard/threading/… ma kilka całkiem dobrych przykładów użycia CancellationTokenSourcez metodami asynchronicznymi, długotrwałych metod z odpytywaniem i używania wywołania zwrotnego.
Ehtesh Choudhury
W tym artykule przedstawiono opcje, które masz i musisz obsługiwać token w zależności od konkretnego przypadku.
Ognyan Dimitrov

Odpowiedzi:

140

Możesz zaimplementować swoją metodę pracy w następujący sposób:

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

Otóż ​​to. Zawsze musisz samodzielnie załatwić anulowanie - wyjdź z metody, gdy nadejdzie odpowiedni czas na wyjście (aby Twoja praca i dane były spójne)

AKTUALIZACJA: Wolę nie pisać, while (!cancelToken.IsCancellationRequested)ponieważ często jest kilka punktów wyjścia, w których można bezpiecznie zatrzymać wykonywanie w treści pętli, a pętla zwykle ma jakiś logiczny warunek do zakończenia (iteracja po wszystkich elementach w kolekcji itp.). Dlatego uważam, że lepiej nie mieszać tych warunków, ponieważ mają one różne intencje.

Ostrzeżenie dotyczące unikania CancellationToken.ThrowIfCancellationRequested():

Komentarz w pytaniu przez Eamon Nerbonne :

... zastępując ThrowIfCancellationRequestedz IsCancellationRequestedgracją kontroli wyjść z gracją, jak mówi ta odpowiedź. Ale to nie jest tylko szczegół implementacji; który wpływa na obserwowalne zachowanie: zadanie nie kończy się już w stanie anulowanym, ale w RanToCompletion. A to może mieć wpływ nie tylko na jawne kontrole stanu, ale także, bardziej subtelnie, na tworzenie łańcuchów zadań, np. W ContinueWithzależności od TaskContinuationOptionsużywanego. Powiedziałbym, że unikanie ThrowIfCancellationRequestedjest niebezpieczną radą.

Sasha
źródło
1
Dzięki! Nie wynika to z tekstu dostępnego online, dość autorytatywnego (książka „C # 4.0 w pigułce”?), Który cytowałem. Czy możesz mi podać odniesienie do „zawsze”?
Pełna wersja
1
To pochodzi z praktyki i doświadczenia =). Nie pamiętam, skąd to wiem. Użyłem „zawsze potrzebujesz”, ponieważ faktycznie możesz przerwać wątek roboczy z wyjątkiem z zewnątrz za pomocą Thread.Abort (), ale to bardzo zła praktyka. Nawiasem mówiąc, użycie CancellationToken.ThrowIfCancellationRequested () jest również „samodzielną obsługą anulowania”, po prostu jest to inny sposób.
Sasha,
1
@OleksandrPshenychnyy Miałem na myśli zamianę while (true) na while (! CancelToken.IsCancellationRequested). To było pomocne! Dzięki!
Doug Dawson
1
@Fulproof Nie ma ogólnego sposobu, aby środowisko wykonawcze anulowało działający kod, ponieważ środowiska wykonawcze nie są wystarczająco inteligentne, aby wiedzieć, gdzie można przerwać proces. W niektórych przypadkach można po prostu wyjść z pętli, w innych potrzebna jest bardziej złożona logika, np. Transakcje muszą zostać wycofane, zasoby muszą zostać zwolnione (np. Uchwyty plików lub połączenia sieciowe). Dlatego nie ma magicznego sposobu na anulowanie zadania bez konieczności napisania kodu. To, o czym myślisz, przypomina zabijanie procesu, ale to nie jest anulowanie, to jedna z najgorszych rzeczy, które mogą się przydarzyć aplikacji, ponieważ nie można jej wyczyścić.
user3285954
1
@kosist Możesz użyć CancellationToken.None, jeśli nie planujesz anulować operacji, którą uruchamiasz ręcznie. Oczywiście po zabiciu procesu systemowego wszystko zostaje przerwane i CancellationToken nie ma z tym nic wspólnego. Więc tak, powinieneś utworzyć CancellationTokenSource tylko wtedy, gdy naprawdę potrzebujesz go użyć do anulowania operacji. Nie ma sensu tworzyć czegoś, czego nie używasz.
Sasha
26

@ BrainSlugs83

Nie powinieneś ślepo ufać wszystkiemu, co zostało opublikowane w stackoverflow. Komentarz w kodzie Jensa jest niepoprawny, parametr nie kontroluje, czy wyjątki są generowane, czy nie.

MSDN jasno określa, co kontroluje ten parametr, czytałeś? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Jeśli throwOnFirstExceptionjest true, wyjątek zostanie natychmiast propagowany z wywołania Cancel, zapobiegając przetwarzaniu pozostałych wywołań zwrotnych i operacji anulowania. Jeśli throwOnFirstExceptionma wartość false, to przeciążenie spowoduje agregację wszystkich wyjątków zgłoszonych do elementu an AggregateException, tak że jedno wywołanie zwrotne zgłaszające wyjątek nie uniemożliwi wykonania innych zarejestrowanych wywołań zwrotnych.

Nazwa zmiennej jest również nieprawidłowa, ponieważ Cancel jest wywoływana na CancellationTokenSourcesamym tokenie, a nie na samym tokenie, a źródło zmienia stan każdego tokenu, którym zarządza.

user3285954
źródło
Zapoznaj się
Epstone
1
To bardzo przydatna informacja, ale w ogóle nie odpowiada na zadane pytanie.
11nallan11
16

Możesz utworzyć zadanie z tokenem anulowania, kiedy aplikacja przejdzie do tła, możesz anulować ten token.

Możesz to zrobić w 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);

Rozwiązanie Anther to licznik czasu użytkownika w Xamarin.Forms, zatrzymaj licznik czasu, gdy aplikacja przejdzie w tło https://xamarinhelp.com/xamarin-forms-timer/

Jesse Jiang
źródło
10

Państwo może używać ThrowIfCancellationRequestedbez obsługi wyjątku!

Użycie z ThrowIfCancellationRequestedma być używane od wewnątrz a Task(nie a Thread). Gdy jest używany w ramach Task, nie musisz samodzielnie obsługiwać wyjątku (i otrzymywać błędu nieobsługiwanego wyjątku). Spowoduje to opuszczenie Task, a Task.IsCancelledwłaściwość będzie miała wartość True. Nie jest wymagana obsługa wyjątków.

W twoim przypadku szczególnego, zmień ThreadDo Task.

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

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}
Titus
źródło
Dlaczego używasz, t.Start()a nie Task.Run()?
Xander Luciano,
1
@XanderLuciano: W tym przykładzie nie ma konkretnego powodu, a Task.Run () byłaby lepszym wyborem.
Titus,
5

Musisz przekazać CancellationTokenzadanie do zadania, które będzie okresowo monitorować token, aby sprawdzić, czy żądane jest anulowanie.

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

W takim przypadku operacja zakończy się, gdy zażąda się anulowania, a Taskbędzie miał RanToCompletionstan. Jeśli chcesz otrzymać potwierdzenie, że Twoje zadanie zostało anulowane , musisz użyć, ThrowIfCancellationRequestedaby zgłosićOperationCanceledException wyjątek.

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

Mam nadzieję, że to pomoże lepiej zrozumieć.

Mahbubur Rahman
źródło