2015-12-15 622 views
8

In unserer Anwendung arbeiten wir viel mit async/await und Tasks. Daher verwendet es häufig Task.Run, manchmal mit Unterstützung für die Löschung unter Verwendung der eingebauten CancellationToken.TaskCancellationException, wie die Ausnahme im Erfolgskontrollfluss vermieden werden kann?

public Task DoSomethingAsync(CancellationToken cancellationToken) 
{ 
    return Task.Run(() => 
    { 
     while (true) 
     { 
      if (cancellationToken.IsCancellationRequested) break; 
      //do some work 
     } 
    }, cancellationToken); 
} 

Wenn ich die Ausführung stornieren nun die CancellationToken die Ausführung mit am Anfang der nächsten Schleife stoppt, oder wenn die Aufgabe überhaupt nicht starten wirft es eine Ausnahme (TaskCanceledException innerhalb Task.Run). Die Frage ist nun, warum Task.Run eine Exception verwendet, um die erfolgreiche Löschung zu steuern, anstatt nur eine abgeschlossene Aufgabe zurückzugeben. Gibt es einen bestimmten Grund, warum MS sich nicht an die Regel "KEINE AUSNAHMEN ZUR KONTROLLE DES AUSFÜHRUNGSFLUSSES VERWENDEN" gehalten hat?

Und wie kann ich verhindern, dass jede Methode, die Stornierung (die eine Menge sind) in einem völlig nutzlosen Versuch Catch (TaskCancelledException) Block Boxing unterstützt?

+2

" Stornierung! = Erfolgreicher Abschluss ". Was würden Sie erwarten, dass die Aufgabe 'zurückgegeben wird, wenn sie abgebrochen wird? –

+0

Eine Aufgabeninstanz, die mit ** cancellationTokenSource.Cancel() abgebrochen wird; ** hat den Status ** TaskStatus.RanToCompletion **, nicht den Status ** TaskStatus.Canceled **. –

+0

Sie haben Recht für Aufgaben mit Rückgabewert, für die eine Ausnahme erforderlich ist. Hab nicht darüber nachgedacht. – Console

Antwort

5

Nun, Sie können nicht wirklich den Unterschied in Ihrem sehr einfachen Szenario sehen - Sie verwenden nicht das Ergebnis der Task, und Sie müssen die Stornierung nicht durch eine komplexe Aufrufliste propagieren.

Zuerst kann Ihr Task einen Wert zurückgeben. Was geben Sie zurück, wenn der Vorgang abgebrochen wurde?

Zweitens können weitere Aufgaben nach der abgebrochenen Aufgabe ausgeführt werden. Wahrscheinlich möchten Sie die Stornierung durch die anderen Aufgaben nach Belieben verbreiten.

Ausnahmen propagieren. Aufgabenlöschung ist ziemlich identisch mit Thread.Abort in dieser Verwendung - wenn Sie eine Thread.Abort ausgeben, wird eine ThreadAbortException verwendet, um sicherzustellen, dass Sie den ganzen Weg zurück nach oben entspannen. Andernfalls müssten alle Ihre Methoden das Ergebnis jeder aufgerufenen Methode überprüfen, prüfen, ob sie abgebrochen wurde, und sich bei Bedarf selbst zurückgeben - und wir haben bereits gesehen, dass die Leute die Fehlerrückgabewerte im alten C ignorieren:

Am Ende ist Task-Cancelling, ebenso wie Thread-Abbrüche, ein außergewöhnliches Szenario. Es beinhaltet bereits Synchronisation, Stackabwickeln usw.

Dies bedeutet jedoch nicht, dass Sie unbedingt try-catch verwenden müssen, um die Ausnahme zu erfassen - Sie können Taskstatus verwenden. Zum Beispiel können Sie eine Hilfsfunktion wie folgt verwenden:

public static Task<T> DefaultIfCanceled<T>(this Task<T> @this, T defaultValue = default(T)) 
{ 
    return 
    @this.ContinueWith 
     (
     t => 
     { 
      if (t.IsCanceled) return defaultValue; 

      return t.Result; 
     } 
    ); 
} 

, die Sie als

await SomeAsync().DefaultIfCanceled(); 

Natürlich können, ist zu beachten, dass niemand Sie zwingt diese Methode der Stornierung zu verwenden - es ist einfach als Annehmlichkeit zur Verfügung gestellt. Sie können beispielsweise Ihren eigenen verstärkten Typ verwenden, um die Stornierungsinformationen beizubehalten, und die Stornierung manuell vornehmen. Aber wenn Sie damit anfangen, werden Sie den Grund finden, warum die Löschung mit Ausnahmen gehandhabt wird - dies im imperativen Code zu tun ist ein Schmerz, so dass Sie entweder viel Mühe für keinen Gewinn verschwenden, oder Sie werden zu einem wechseln mehr funktionale Art der Programmierung (komm, wir haben Cookies! *).

(*) Haftungsausschluss: Wir haben keine Cookies. Aber Sie können Ihre eigenen machen!

0

Die Ausnahme wird für einen Zweck geworfen, wie andere in der Gemeinschaft bereits darauf hingewiesen haben.

Wenn Sie jedoch mehr Kontrolle über TaskCanceledException Verhalten haben mögen und haben immer noch die Logik zu einem Ort isoliert können Sie eine Erweiterungsmethode implementieren Task auszudehnen, die Stornierung behandelt, so etwas wie diese -

public async Task DoSomethingAsync(CancellationToken cancellationToken) 
    { 
     await Task.Run(() => 
     { 
      while (true) 
      { 
       if (cancellationToken.IsCancellationRequested) break; 
       //do some work 
      } 
     }). 
     WithCancellation(cancellationToken,false); // pass the cancellation token to extension funciton instead to run 
    } 



static class TaskCacellationHelper 
{ 
    private struct Void { } // just to support TaskCompletionSource class. 


    public static async Task WithCancellation(this Task originalTask, CancellationToken ct, bool suppressCancellationExcetion) 
    { 
     // Create a Task that completes when the CancellationToken is canceled 
     var cancelTask = new TaskCompletionSource<Void>(); 
     // When the CancellationToken is canceled, complete the Task 
     using (ct.Register(
     t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask)) 
     { 
      // Create a Task that completes when either the original or 
      // CancellationToken Task completes 
      Task any = await Task.WhenAny(originalTask, cancelTask.Task); 
      // If any Task completes due to CancellationToken, throw OperationCanceledException 
      if (any == cancelTask.Task) 
      { 
       // 
       if (suppressCancellationExcetion == false) 
       { 
        ct.ThrowIfCancellationRequested(); 
       } 
       else 
       { 
        Console.WriteLine("Cancelled but exception supressed"); 
       } 
      } 
     } 
     // await original task. Incase of cancellation your logic will break the while loop    
     await originalTask; 
    } 
}