2015-02-20 9 views
13

Wenn ich meine asynchrone Methode mit dem folgenden Inhalt abbricht, indem ich die Cancel() Methode meiner CancellationTokenSource aufruft, wird es schließlich aufhören. Da jedoch die Zeile Console.WriteLine(await reader.ReadLineAsync()); ziemlich lange dauert, habe ich versucht, auch mein CancellationToken an ReadLineAsync() zu übergeben (in der Erwartung, dass es eine leere Zeichenfolge zurückgibt), damit die Methode besser auf meinen Cancel()-Aufruf reagiert. Allerdings konnte ich keine CancellationToken an ReadLineAsync() übergeben.Kann ich StreamReader.ReadLineAsync mit einem CancellationToken abbrechen?

Kann ich einen Anruf zu Console.WriteLine() oder Streamreader.ReadLineAsync() abbrechen und wenn ja, wie mache ich das?

Warum akzeptiert ReadLineAsync() keine CancellationToken? Ich hielt es für eine gute Methode, den Async-Methoden einen optionalen Parameter CancellationToken zu geben, auch wenn die Methode nach dem Abbrechen noch abgeschlossen ist.

StreamReader reader = new StreamReader(dataStream); 
while (!reader.EndOfStream) 
{ 
    if (ct.IsCancellationRequested){ 
     ct.ThrowIfCancellationRequested(); 
     break; 
    } 
    else 
    { 
     Console.WriteLine(await reader.ReadLineAsync()); 
    } 
} 

aktualisiert Wie unten in den Kommentaren erwähnt, allein der Console.WriteLine() Aufruf wurde bereits einige Sekunden aufgrund eines schlecht formated Eingabestring von 40.000 Zeichen pro Zeile einnimmt. Das Auflösen löst meine Probleme mit der Antwortzeit, aber ich bin immer noch an irgendwelchen Vorschlägen oder Problemumgehungen interessiert, wie man diese lang andauernde Anweisung abbrechen kann, wenn aus irgendeinem Grund 40.000 Zeichen in eine Zeile geschrieben werden sollen (zum Beispiel wenn man die ganze Zeichenkette in eine Datei).

+3

Sie versuchen, das falsche Problem zu lösen. Sie haben ein überaus benutzerfreundliches Programm geschrieben, es ist verrückt scrollen Text über den Bildschirm mit einer Rate weit höher, als der Benutzer jemals lesen kann. Jetzt suchen Sie nach der Lösung "Stoppt diesen Unsinn". Natürlich sind Sie, Ihr Benutzer würde auch. Aber das ist nicht die wirkliche Lösung, die richtige wird es nie von vorn anfangen. Schreiben Sie es in eine Datei, zeigen Sie es mit dem Editor an. Alles ist besser. –

+0

'Sie versuchen, das falsche Problem zu lösen. 'Danke, dass Sie so klar sind - Sie haben absolut Recht. –

Antwort

6

Sie können den Vorgang nur abbrechen, wenn er stornierbar ist. Sie können die WithCancellation Erweiterungsmethode verwenden Sie den Code Fluss verhalten zu haben, als ob es abgebrochen wurde, aber die zugrunde liegenden würde noch laufen:

public static Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken) 
{ 
    return task.IsCompleted // fast-path optimization 
     ? task 
     : task.ContinueWith(
      completedTask => completedTask.GetAwaiter().GetResult(), 
      cancellationToken, 
      TaskContinuationOptions.ExecuteSynchronously, 
      TaskScheduler.Default); 
} 

Verbrauch:

await task.WithCancellation(cancellationToken); 

Sie nicht Console.WriteLine abbrechen und Sie muss nicht. Es ist sofort, wenn Sie eine vernünftige Größe string haben.

Über die Richtlinie: Wenn Ihre Implementierung die Stornierung nicht unterstützt, sollten Sie kein Token akzeptieren, da es eine gemischte Nachricht sendet.

Wenn Sie eine riesige Zeichenfolge zum Schreiben auf die Konsole haben, sollten Sie nicht verwenden. Sie können die Zeichenfolge in einem Zeichen in einer Zeit zu schreiben und diese Methode kündbar sein:

public void DumpHugeString(string line, CancellationToken token) 
{ 
    foreach (var character in line) 
    { 
     token.ThrowIfCancellationRequested(); 
     Console.Write(character); 
    } 

    Console.WriteLine(); 
} 

Eine noch bessere Lösung wäre es, in den Reihen zu schreiben anstatt einzelner Zeichen.Hier ist eine Implementierung mit MoreLinq ‚s Batch:

public void DumpHugeString(string line, CancellationToken token) 
{ 
    foreach (var characterBatch in line.Batch(100)) 
    { 
     token.ThrowIfCancellationRequested(); 
     Console.Write(characterBatch.ToArray()); 
    } 

    Console.WriteLine(); 
} 

So, zum Schluss:

var reader = new StreamReader(dataStream); 
while (!reader.EndOfStream) 
{ 
    DumpHugeString(await reader.ReadLineAsync().WithCancellation(token), token); 
} 
+0

@HW Sie können 'Console.WriteLine' nicht abbrechen, es sei denn, es unterstützt die Löschung, aber Sie können' Console.Write' Stück für Stück verwenden. Siehe mein Update. – i3arnon

+0

Sind Sie sicher, dass 'Task.ContinueWith' die gesamten Aufgaben ebenfalls abbricht? Aus meiner Sicht ist Task.ContinueWith hier nicht geeignet. Sie benötigen etwas wie "Task.WhenAny (task, infiniteCancellableTaks)" wobei 'var infiniteCancellableTaks = Task.Delay (Timeout.Infinite, Token)', wie in diesem Beispiel http://stackoverflow.com/a/23473779/2528649 – neleus

+0

@ Neleus Ja. Es wird die ursprüngliche Aufgabe nicht abbrechen (weil Sie nicht können), aber es wird die Fortsetzung abbrechen. Fühlen Sie sich frei, es auszuprobieren. – i3arnon

0

Sie können Streamreader.ReadLineAsync() nicht abbrechen. IMHO ist das, weil das Lesen einer einzelnen Linie sehr schnell sein sollte. Sie können jedoch problemlos verhindern, dass Console.WriteLine() passiert, indem Sie eine separate Taskvariable verwenden.

Der Scheck für ct.IsCancellationRequested ist auch redundant, da ct.ThrowIfCancellationRequested() nur geworfen wird, wenn eine Stornierung angefordert wird.

StreamReader reader = new StreamReader(dataStream); 
while (!reader.EndOfStream) 
{ 
    ct.ThrowIfCancellationRequested(); 
    string line = await reader.ReadLineAsync()); 

    ct.ThrowIfCancellationRequested();  
    Console.WriteLine(line); 
} 
+0

Vielen Dank für den Hinweis, dass ich nicht nach "IsCancellationRequested" suchen muss! Leider hat die erneute Überprüfung auf den Abbruch zwischen den beiden Aufgaben meine Reaktionszeit kaum verringert. –

+2

Das Lesen einer einzelnen Zeile kann sehr langsam sein, wenn Sie beispielsweise einen Stdout-Stream oder einen Netzwerkstream lesen, d. H. Einen Stream, dessen Inhalt nicht vollständig in einer Aufnahme materialisiert ist. – dcstraw

1

ich diesen answer auf diese verallgemeinert:

public static async Task<T> WithCancellation<T>(this Task<T> task, CancellationToken cancellationToken, Action action, bool useSynchronizationContext = true) 
{ 
    using (cancellationToken.Register(action, useSynchronizationContext)) 
    { 
     try 
     { 
      return await task; 
     } 
     catch (Exception ex) 
     { 

      if (cancellationToken.IsCancellationRequested) 
      { 
       // the Exception will be available as Exception.InnerException 
       throw new OperationCanceledException(ex.Message, ex, cancellationToken); 
      } 

      // cancellation hasn't been requested, rethrow the original Exception 
      throw; 
     } 
    } 
} 

Jetzt können Sie Ihre Stornierung verwenden Token auf jede abbrechbare asynchrone Methode. Zum Beispiel WebRequest.GetResponseAsync:

var request = (HttpWebRequest)WebRequest.Create(url); 

using (var response = await request.GetResponseAsync()) 
{ 
    . . . 
} 

wird geworden:

var request = (HttpWebRequest)WebRequest.Create(url); 

using (WebResponse response = await request.GetResponseAsync().WithCancellation(CancellationToken.None, request.Abort, true)) 
{ 
    . . . 
} 

Siehe Beispiel http://pastebin.com/KauKE0rW

+0

Es gibt ein Rennen in dem Beispiel. Wenn Sie im Catch-Handler "IsCancellationRequested" aktivieren, wissen Sie nicht, ob die Stornierung oder Ausnahme zuerst aufgetreten ist. Sie sollten nach dem Ausnahmetyp suchen, um festzustellen, ob es sich tatsächlich um eine Ausnahmebedingung handelt. – Kenneth

Verwandte Themen