Wie verwende ich die CancellationToken-Eigenschaft?

116

Im Vergleich zum vorherigen Code für die Klasse RulyCanceler wollte ich Code mit ausführen CancellationTokenSource.

Wie verwende ich es wie in Stornierungs-Token erwähnt , dh ohne eine Ausnahme zu werfen / zu fangen? Kann ich die IsCancellationRequestedImmobilie nutzen?

Ich habe versucht, es so zu verwenden:

cancelToken.ThrowIfCancellationRequested();

und

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

Dies führte jedoch zu einem Laufzeitfehler cancelToken.ThrowIfCancellationRequested();in der Methode 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:

Der Code, den ich erfolgreich ausgeführt habe, hat die OperationCanceledException im neuen Thread abgefangen:

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
quelle
2
docs.microsoft.com/en-us/dotnet/standard/threading/… enthält einige ziemlich gute Beispiele für die Verwendung CancellationTokenSourcemit asynchronen Methoden, lang laufenden Methoden mit Abfragen und die Verwendung eines Rückrufs.
Ehtesh Choudhury
Dieser Artikel zeigt die Optionen, die Sie haben und benötigen, um das Token entsprechend Ihrem speziellen Fall zu behandeln.
Ognyan Dimitrov

Antworten:

139

Sie können Ihre Arbeitsmethode wie folgt implementieren:

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

Das ist es. Sie müssen die Stornierung immer selbst vornehmen - beenden Sie die Methode, wenn es angemessen ist, sie zu beenden (damit Ihre Arbeit und Ihre Daten in einem konsistenten Zustand sind).

UPDATE: Ich schreibe lieber nicht, while (!cancelToken.IsCancellationRequested)da es oft nur wenige Exit-Punkte gibt, an denen Sie die sichere Ausführung über den Loop-Body hinweg beenden können, und Loop normalerweise eine logische Bedingung zum Beenden hat (Iteration über alle Elemente in der Sammlung usw.). Ich glaube, es ist besser, diese Bedingungen nicht zu mischen, da sie unterschiedliche Absichten haben.

Warnhinweis zur Vermeidung CancellationToken.ThrowIfCancellationRequested():

Kommentar von Eamon Nerbonne :

... durch ThrowIfCancellationRequestedeine Reihe von Schecks für IsCancellationRequestedExits würdevoll ersetzen , wie diese Antwort sagt. Dies ist jedoch nicht nur ein Implementierungsdetail. Dies wirkt sich auf das beobachtbare Verhalten aus: Die Aufgabe endet nicht mehr im abgebrochenen Zustand, sondern in RanToCompletion. Dies kann sich nicht nur auf explizite Statusprüfungen auswirken, sondern auch auf subtilere Weise ContinueWithauf die Verkettung von Aufgaben, z TaskContinuationOptions. Ich würde sagen, dass das Vermeiden ThrowIfCancellationRequestedein gefährlicher Rat ist.

Sasha
quelle
1
Vielen Dank! Dies folgt nicht aus dem von mir zitierten Online-Text (Buch "C # 4.0 in a Nutshell"?). Könnten Sie mir einen Hinweis zu "immer" geben?
Fulproof
1
Dies kommt aus der Praxis und Erfahrung =). Ich kann mich nicht erinnern, woher ich das weiß. Ich habe "Sie brauchen immer" verwendet, weil Sie den Arbeitsthread mit Ausnahme von außen mit Thread.Abort () tatsächlich unterbrechen können, aber das ist eine sehr schlechte Praxis. Übrigens bedeutet die Verwendung von CancellationToken.ThrowIfCancellationRequested () auch "Stornierung selbst erledigen", genau umgekehrt.
Sasha
1
@OleksandrPshenychnyy Ich meinte ersetzen while (true) durch while (! CancelToken.IsCancellationRequested). Das war hilfreich! Vielen Dank!
Doug Dawson
1
@Fulproof Es gibt keine generische Möglichkeit für eine Laufzeit, laufenden Code abzubrechen, da die Laufzeiten nicht intelligent genug sind, um zu wissen, wo ein Prozess unterbrochen werden kann. In einigen Fällen ist es möglich, eine Schleife einfach zu verlassen, in anderen Fällen ist eine komplexere Logik erforderlich, dh Transaktionen müssen zurückgesetzt werden, Ressourcen müssen freigegeben werden (z. B. Dateihandles oder Netzwerkverbindungen). Aus diesem Grund gibt es keine magische Möglichkeit, eine Aufgabe abzubrechen, ohne Code schreiben zu müssen. Was Sie denken, ist wie das Beenden eines Prozesses, aber das ist kein Abbruch. Dies ist eines der schlimmsten Dinge, die einer Anwendung passieren können, weil sie nicht bereinigt werden kann.
user3285954
1
@kosist Sie können CancellationToken.None verwenden, wenn Sie nicht vorhaben, den manuell gestarteten Vorgang abzubrechen. Wenn der Systemprozess beendet wird, wird natürlich alles unterbrochen und CancellationToken hat damit nichts zu tun. Ja, Sie sollten CancellationTokenSource nur erstellen, wenn Sie es wirklich benötigen, um den Vorgang abzubrechen. Es macht keinen Sinn, etwas zu erschaffen, das Sie nicht verwenden.
Sasha
26

@ BrainSlugs83

Sie sollten nicht blindlings auf alles vertrauen, was im Stackoverflow veröffentlicht wurde. Der Kommentar im Jens-Code ist falsch. Der Parameter steuert nicht, ob Ausnahmen ausgelöst werden oder nicht.

MSDN ist sehr klar, was dieser Parameter steuert. Haben Sie es gelesen? http://msdn.microsoft.com/en-us/library/dd321703(v=vs.110).aspx

Wenn dies der throwOnFirstExceptionFall ist, wird eine Ausnahme sofort aus dem Aufruf zum Abbrechen weitergegeben, wodurch verhindert wird, dass die verbleibenden Rückrufe und abbrechbaren Vorgänge verarbeitet werden. Wenn dies throwOnFirstExceptionfalsch ist, werden durch diese Überladung alle Ausnahmen zusammengefasst, die in a ausgelöst werden AggregateException, sodass ein Rückruf, der eine Ausnahme auslöst, die Ausführung anderer registrierter Rückrufe nicht verhindert.

Der Variablenname ist auch falsch, da Abbrechen CancellationTokenSourcenicht für das Token selbst aufgerufen wird und die Quelle den Status jedes von ihr verwalteten Tokens ändert.

user3285954
quelle
Schauen Sie sich auch die Dokumentation (TAP) hier über die vorgeschlagene Verwendung des Stornierungs-Tokens an: docs.microsoft.com/en-us/dotnet/standard/…
Epstone
1
Dies sind sehr nützliche Informationen, die jedoch die gestellte Frage überhaupt nicht beantworten.
11nallan11
16

Sie können eine Aufgabe mit einem Stornierungs-Token erstellen. Wenn Sie zum Hintergrund der App wechseln, können Sie dieses Token abbrechen.

Sie können dies in PCL https://developer.xamarin.com/guides/xamarin-forms/application-fundamentals/app-lifecycle tun

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

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

Eine weitere Lösung ist der Benutzer-Timer in Xamarin.Forms. Stoppen Sie den Timer, wenn die App zum Hintergrund wechselt. Https://xamarinhelp.com/xamarin-forms-timer/

Jesse Jiang
quelle
10

Sie können verwenden, ThrowIfCancellationRequestedohne die Ausnahme zu behandeln!

Die Verwendung von ThrowIfCancellationRequestedsoll innerhalb von a Task(nicht a Thread) verwendet werden. Bei Verwendung innerhalb von a Taskmüssen Sie die Ausnahme nicht selbst behandeln (und erhalten den Fehler "Nicht behandelte Ausnahme"). Dies führt dazu, dass Taskdie Task.IsCancelledEigenschaft verlassen wird und die Eigenschaft True ist. Keine Ausnahmebehandlung erforderlich.

Ändern Sie in Ihrem speziellen Fall das Threadin a Task.

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

if (t.IsCancelled)
{
    Console.WriteLine("Canceled!");
}
Titus
quelle
Warum benutzt du t.Start()und nicht Task.Run()?
Xander Luciano
1
@ XanderLuciano: In diesem Beispiel gibt es keinen bestimmten Grund, und Task.Run () wäre die bessere Wahl gewesen.
Titus
5

Sie müssen das CancellationTokenan die Aufgabe übergeben, die das Token regelmäßig überwacht, um festzustellen, ob eine Stornierung angefordert wird.

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 diesem Fall endet die Operation , wenn Annullierung angefordert wird und die Taskeinen haben RanToCompletionZustand. Wenn Sie bestätigt werden möchten, dass Ihre Aufgabe abgebrochen wurde , müssen Sie ThrowIfCancellationRequestedeine OperationCanceledExceptionAusnahme auslösen.

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

Hoffe das hilft besser zu verstehen.

Mahbubur Rahman
quelle