2017-08-29 8 views
1

Ich habe ein Problem beim ordnungsgemäßen Abbrechen einer asynchronen Aufgabe festgestellt.Gibt es eine ordnungsgemäße Möglichkeit, eine asynchrone Aufgabe abzubrechen?

Hier ist ein Entwurf.

Mein Einstiegspunkt führt zwei asynchrone Aufgaben aus. Die erste Aufgabe erledigt "lange" Arbeit und die zweite Aufgabe bricht sie ab.

Einstiegspunkt:

private static void Main() 
{ 
    var ctc = new CancellationTokenSource(); 

    var cancellable = ExecuteLongCancellableMethod(ctc.Token); 

    var cancelationTask = Task.Run(() => 
    { 
     Thread.Sleep(2000); 

     Console.WriteLine("[Before cancellation]"); 

     ctc.Cancel(); 
    }); 

    try 
    { 
     Task.WaitAll(cancellable, cancelationTask); 
    } 
    catch (Exception e) 
    { 
     Console.WriteLine($"An exception occurred with type {e.GetType().Name}"); 
    } 
} 

Methode, die Aufgabe zurückgegeben-able Abbrechen

private static Task ExecuteLongCancellableMethod(CancellationToken token) 
{ 
    return Task.Run(() => 
    { 
     token.ThrowIfCancellationRequested(); 

     Console.WriteLine("1st"); 
     Thread.Sleep(1000); 

     Console.WriteLine("2nd"); 
     Thread.Sleep(1000); 

     Console.WriteLine("3rd"); 
     Thread.Sleep(1000); 

     Console.WriteLine("4th"); 
     Thread.Sleep(1000); 

     Console.WriteLine("[Completed]"); 

    }, token); 
} 

Mein Ziel '1.' zu stoppen, ist das Schreiben, '2.', '3.' sofort nach Absage wird angerufen. Aber ich habe folgende Ergebnisse:

1st 
2nd 
3rd 
[Before cancellation] 
4th 
[Completed] 

Aus offensichtlichen Grund, warum ich nicht eine Ausnahme erhalten, die auslöst, wenn Annullierung angefordert wird. Also habe ich versucht Methode neu zu schreiben, wie folgend:

private static Task ExecuteLongCancellableAdvancedMethod(CancellationToken token) 
{ 
    return Task.Run(() => 
    { 
     var actions = new List<Action> 
     { 
      () => Console.WriteLine("1st"), 
      () => Console.WriteLine("2nd"), 
      () => Console.WriteLine("3rd"), 
      () => Console.WriteLine("4th"), 
      () => Console.WriteLine("[Completed]") 
     }; 

     foreach (var action in actions) 
     { 
      token.ThrowIfCancellationRequested(); 

      action.Invoke(); 

      Thread.Sleep(1000); 
     } 

    }, token); 
} 

Und jetzt habe ich, was ich will:

1st 
2nd 
[Before cancellation] 
3rd 
An exception occurred with type AggregateException 

aber ich denke, die Schaffung einer Sammlung von Action-Delegierten und Looping durch nicht das bequemste ist Möglichkeit, mit meinem Problem umzugehen.

Also, was ist der richtige Weg, es zu tun? Und warum muss ich mein Abbruch-Token in die Task.Run-Methode als zweites Argument übergeben?

+0

Wenn die Stornierung für die Token bereits angefordert worden ist, 'Task.Run' nicht die Delegierten läuft auf all –

+1

Sie müssen explizit nennen' token.ThrowIfCancellationRequested(); 'an jedem Ort, wo Sie wollen die Ausführung, um tatsächlich stornierbar zu sein. Also im ersten Beispiel vor jeder 'Console.WriteLine'. –

+1

Ich empfehle dringend, dass Sie auch auf die ContinueWith-Methode nachlesen, da Sie nach dem "richtigen" Weg gefragt haben. Ich denke, Sie werden sich mit ContinueWith und TaskContinuationOptions.NotOnCanceled vertraut machen. Hier ist ein Link mit mehr hilfreiche Informationen: https://social.msdn.microsoft.com/Forums/en-US/310782d8-aadf-4841-a309-23abff407d9a/cancellation-using-exception-to-control-application-flow? forum = Parallelextensions – Jace

Antwort

2

Die Task wird nicht selbst abbrechen, es liegt an Ihnen, die Annullierungsanfrage zu erkennen und Ihre Arbeit sauber abzubrechen. Das macht token.ThrowIfCancellationRequested();.

Sie sollten diese Überprüfungen im gesamten Code an Orten vornehmen, an denen die Ausführung sauber gestoppt oder in einen sicheren Zustand zurückgesetzt werden kann.

In Ihrem zweiten Beispiel rufen Sie es einmal pro Wiederholung der Schleife, und es funktioniert gut. Das erste Beispiel ruft es nur einmal am Anfang auf. Wenn das Token bis zu diesem Zeitpunkt nicht abgebrochen wurde, wird die Aufgabe wie beschrieben ausgeführt.

Wenn Sie es so geändert haben, sehen Sie auch die erwarteten Ergebnisse.

return Task.Run(() => 
{ 
    token.ThrowIfCancellationRequested(); 
    Console.WriteLine("1st"); 
    Thread.Sleep(1000); 

    token.ThrowIfCancellationRequested(); 
    Console.WriteLine("2nd"); 
    Thread.Sleep(1000); 

    token.ThrowIfCancellationRequested(); 
    Console.WriteLine("3rd"); 
    Thread.Sleep(1000); 

    token.ThrowIfCancellationRequested(); 
    Console.WriteLine("4th"); 
    Thread.Sleep(1000); 

    Console.WriteLine("[Completed]"); 

}, token); 
+0

Danke, ich weiß, wie der Code, den ich geschrieben habe, funktioniert, aber gibt es noch bequemere Möglichkeiten, eine Aufgabe abzubrechen?Ich meine, "ThrowIfCancellationRequested" zu schreiben, nachdem jeder logische Block Code sieht, der für mich schrecklich aussieht. –

+1

Nein, ich kenne keine Möglichkeit. Nur der Entwickler kann wissen, wo die sicheren Stellen im Code die "Aufgabe" sauber löschen sollen. Der Code innerhalb der 'Task' könnte Variablen und Daten in einem unsauberen, unbekannten Zustand hinterlassen, wenn er irgendwie abbrechen würde, sobald die Löschung angefordert wird, und [das könnte genauso gefährlich sein wie' Thread.Abort'] (https : //stackoverflow.com/questions/1559255/whats-wrong-with-using-thread-abort). Stellen Sie sich vor, was passiert, wenn die Aufgabe eine Aufgabe für ein Objekt übernimmt und abgebrochen wird, bevor es freigegeben werden kann, oder wenn die Aufgabe mit nicht verwaltetem Speicher zu tun hat –

Verwandte Themen