2015-02-20 10 views
13

Quando annullo il mio metodo asincrono con il seguente contenuto chiamando il metodo Cancel() del mio CancellationTokenSource, si fermerà alla fine. Tuttavia, poiché la riga Console.WriteLine(await reader.ReadLineAsync()); richiede un bel po 'di tempo per completare, ho provato a passare il mio CancellationToken a ReadLineAsync() (aspettandomi di restituire una stringa vuota) per rendere il metodo più reattivo alla mia chiamata Cancel(). Tuttavia non ho potuto passare un CancellationToken a ReadLineAsync().Posso annullare StreamReader.ReadLineAsync con un CancellationToken?

Posso annullare una chiamata a Console.WriteLine() o Streamreader.ReadLineAsync() e, in caso affermativo, come faccio?

Perché ReadLineAsync() non si accetta uno CancellationToken? Ho pensato che fosse una buona pratica dare ai metodi Async un parametro opzionale CancellationToken anche se il metodo dovesse ancora essere completato dopo essere stato cancellato.

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

Aggiornamento Come dichiarato nei commenti qui sotto, la chiamata Console.WriteLine() da solo era già riprendendo alcuni secondi a causa di una stringa di input mal formattati di 40.000 caratteri per riga. Rompere questo down risolve i miei problemi relativi al tempo di risposta, ma sono comunque interessato a qualsiasi suggerimento o soluzione alternativa su come cancellare questa dichiarazione di lunga durata se per qualche ragione si scrivono 40.000 caratteri in una riga (ad esempio quando si riversa l'intera stringa in un file).

+3

Si sta cercando di risolvere il problema sbagliato. Hai scritto un programma estremamente ostile all'utente, è un testo che scorre pazzamente sullo schermo ad un ritmo molto più alto di quanto l'utente possa mai leggere. Ora stai cercando la soluzione "ferma questo non-senso". Certo che lo sei, anche il tuo utente lo farebbe. Ma questa non è la vera soluzione, quella giusta non è mai iniziata in primo luogo. Scriverlo in un file, visualizzarlo con Blocco note. Qualunque cosa è meglio –

+0

'Stai cercando di risolvere il problema sbagliato. Grazie per essere chiaro, hai assolutamente ragione. –

risposta

6

Non è possibile annullare l'operazione a meno che non sia annullabile. È possibile utilizzare il metodo WithCancellation estensione di avere il vostro si comportano flusso di codice come se fosse stato annullato, ma il sottostante sarebbe comunque funzionare:

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

Usage:

await task.WithCancellation(cancellationToken); 

non si può annullare Console.WriteLine e si non è necessario. È istantaneo se hai una dimensione ragionevole string.

Informazioni sulla linea guida: se la propria implementazione non supporta effettivamente la cancellazione, non si deve accettare un token poiché invia un messaggio misto.

Se si dispone di una stringa enorme per scrivere sulla console, non utilizzare Console.WriteLine. È possibile scrivere la stringa in un personaggio alla volta e avere che il metodo sia cancellabile:

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

    Console.WriteLine(); 
} 

Una soluzione ancora migliore sarebbe quella di scrivere in lotti al posto dei singoli caratteri.Ecco un'implementazione utilizzando 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(); 
} 

Quindi, in conclusione:

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

@HW non è possibile cancellare 'Console.WriteLine' a meno che non supporti la cancellazione, ma è possibile utilizzare' Console.Write' poco alla volta. Vedi il mio aggiornamento. – i3arnon

+0

Sei sicuro che 'Task.ContinueWith' annullerà anche l'intero taks? Dal mio punto di vista 'Task.ContinueWith' non è adatto qui. Hai bisogno di qualcosa come 'Task.WhenAny (task, infiniteCancellableTaks)' dove 'var infiniteCancellableTaks = Task.Delay (Timeout.Infinite, token)', come in questo esempio http://stackoverflow.com/a/23473779/2528649 – neleus

+0

@ neleus Sì. Non annullerà l'attività originale (perché non è possibile) ma annullerà la continuazione. Sentiti libero di provarlo. – i3arnon

0

Non è possibile annullare Streamreader.ReadLineAsync(). IMHO questo perché leggere una singola riga dovrebbe essere molto veloce. Ma puoi facilmente impedire che lo Console.WriteLine() si verifichi utilizzando una variabile di attività separata.

Il controllo per ct.IsCancellationRequested è anche ridondante come ct.ThrowIfCancellationRequested() verrà generato solo se è richiesta la cancellazione.

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

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

Grazie per il suggerimento che non ho bisogno di controllare 'IsCancellationRequested'! Tristemente il controllo per la cancellazione tra i due compiti non ha fatto diminuire il mio tempo di risposta. –

+2

La lettura di una singola riga potrebbe essere molto lenta, ad esempio se si sta leggendo un flusso stdout o un flusso di rete, ad esempio un flusso il cui contenuto non è completamente materializzato in un'unica ripresa. – dcstraw

1

ho generalizzato questo answer a questo:

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; 
     } 
    } 
} 

Ora è possibile utilizzare la cancellazione gettone qualsiasi metodo asincrono cancellabile. Per esempio WebRequest.GetResponseAsync:

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

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

diventerà:

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

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

vedi esempio http://pastebin.com/KauKE0rW

+0

C'è una gara nell'esempio. Se si seleziona 'IsCancellationRequested' nel gestore catch, non si sa se l'annullamento o l'eccezione si sono verificati per primi. Si dovrebbe verificare il tipo di eccezione per vedere se si tratta davvero di un'eccezione di cancellazione. – Kenneth