2014-07-27 8 views
11

ho la seguente riga di codice utilizzato per leggere in modo asincrono da un NetworkStream:Task.Factory.FromAsync con CancellationTokenSource

int bytesRead = await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null); 

mi piacerebbe fare lo supportano la cancellazione. Vedo che posso cancel tasks using a CancellationTokenSource, tuttavia non vedo alcun modo in cui posso passarlo a TaskFactory.FromAsync().

È possibile eseguire un annullamento del supporto di attività costruito da FromAsync()?

Modifica: Desidero annullare un'attività che è già in esecuzione.

+0

Giusto per chiarire: io voglio annullare un'attività che potrebbe già essere in esecuzione. – Gigi

+0

Sembra che non ci sia sovraccarico di 'FromAsync' che accetta un token di cancellazione. Una possibile soluzione sarebbe quella di aggiungere un altro livello - avviare la propria azione con 'FromAsync' e quindi utilizzare un altro' Task' che supporta la cancellazione dall'esterno per leggere il flusso, all'interno dell'azione personalizzata. – pasty

+0

Hai una buona ragione per non usare 'NetworkStream.ReadAsync', che supporta' CancellationToken'? – avo

risposta

10

Gigi, purtroppo la natura semantica di FromAsync indica che si sta solo adattando un processo asincrono API di TPL (TPL = Microsoft's Task Parallel Library)

In sostanza, ReadAsync di TPL controlla il comportamento asincrono in sé, mentre FromAsync avvolge solo il comportamento (ma non lo controlla).

Ora poiché l'annullamento è un costrutto TPL specifico e poiché FromAsync non ha alcun controllo sui meccanismi interni del metodo async chiamato, non esiste un modo garantito per cancellare in modo pulito l'attività e assicurarsi che tutte le risorse siano chiuse correttamente (Se è curioso, basta decompilare il metodo;))

In queste situazioni, ha più senso avvolgere la chiamata asincrona in un normale compito e rilevare l'eccezione OperationCancelled, che ti darà l'opportunità di chiudere il tuo stream effettuando le chiamate appropriate.

In breve, la risposta è no, ma non vi è nulla che vi impedisca di creare un metodo sovraccarico generico che sceglierà la strategia corretta per chiudere in modo pulito un flusso in base al tipo.

+0

Potrebbe chiarire il significato di TPL a beneficio dei lettori che potrebbero non sapere cosa significa? – Pharap

+1

Punto valido, grazie, lo farò. –

+1

Grazie, ora nessuno lo farà a Google. – Pharap

6

No, non esiste un modo generico per annullare tale attività. La cancellazione è specifica dell'API.

  • Ad esempio, WebClient ha un metodo Cancel.
  • A Socket o FileStream deve essere Close 'd per annullare una chiamata in sospeso.
  • I client di servizi Web hanno persino diversi modi di interrompere le chiamate.
  • ...

Questo perché il realizzatore del funzionamento IO deve supportare la cancellazione.

Potrebbe sembrare allettante utilizzare NetworkStream.ReadAsync e passare un token di cancellazione ma è Stream.ReadAsync. Quest'ultimo getta via il gettone. Fondamentalmente non supportato.

Stream.ReadAsync è solo il metodo della classe base. Non fa nulla da solo. Le operazioni di IO concreto vengono emesse solo da classi derivate. Quelli devono sostenere la cancellazione in modo nativo. Stream non può fare nulla per costringerli. Succede che NetworkStream non supporta la cancellazione.

Capisco che si desidera annullare l'operazione e lasciare la presa aperta. But it is not possible. (Nota soggettiva: questo è davvero un triste stato di cose. Soprattutto se si considera che Windows supporta l'IO cancellabile a livello Win32.)

Se si desidera continuare rapidamente l'applicazione, anche se l'operazione di I/O non è cancellabile, solo ignorare il risultato di tale compito e continuare. Si tenga presente che alla fine l'IO potrebbe completare e, ad esempio, scaricare i dati dai buffer del socket o causare altri effetti collaterali.

"Annullare ignorando" rende effettivamente indefinita la posizione del flusso. Lo stream diventa inutilizzabile. Questo in realtà non evita la necessità di aprire un nuovo stream. Devi ancora liberarti del vecchio flusso (nella maggior parte dei casi) e riaprirlo. Inoltre, stai introducendo la concorrenza.

5

Come altri hanno già menzionato, non esiste un modo pulito per ottenere quello che stai chiedendo. La nozione di cancellazione era assente dal Asynchronous Programming Model; pertanto, non è stato possibile eseguire il retrofit tramite i convertitori FromAsync.

Tuttavia, è possibile introdurre la cancellazione per il Task che avvolge l'operazione asincrona. Questo non annulla l'operazione sottostante - il tuo NetworkStream continuerà a leggere tutti i byte richiesti dal socket - ma consentirà alla tua applicazione di reagire come se l'operazione fosse annullata, lanciando immediatamente un OperationCanceledException dal tuo await (ed eseguendo qualsiasi registrazione continuazione delle attività). Il risultato dell'operazione sottostante, una volta completato, verrà ignorato.

Si tratta di un metodo di estensione di supporto:

public static class TaskExtensions 
{ 
    public async static Task<TResult> HandleCancellation<TResult>(
     this Task<TResult> asyncTask, 
     CancellationToken cancellationToken) 
    {  
     // Create another task that completes as soon as cancellation is requested. 
     // http://stackoverflow.com/a/18672893/1149773 
     var tcs = new TaskCompletionSource<TResult>(); 
     cancellationToken.Register(() => 
      tcs.TrySetCanceled(), useSynchronizationContext: false); 
     var cancellationTask = tcs.Task; 

     // Create a task that completes when either the async operation completes, 
     // or cancellation is requested. 
     var readyTask = await Task.WhenAny(asyncTask, cancellationTask); 

     // In case of cancellation, register a continuation to observe any unhandled 
     // exceptions from the asynchronous operation (once it completes). 
     // In .NET 4.0, unobserved task exceptions would terminate the process. 
     if (readyTask == cancellationTask) 
      asyncTask.ContinueWith(_ => asyncTask.Exception, 
       TaskContinuationOptions.OnlyOnFaulted | 
       TaskContinuationOptions.ExecuteSynchronously); 

     return await readyTask; 
    } 
} 

e questo è un esempio che utilizza il metodo di estensione per il trattamento di un'operazione come cancellata dopo 300ms:

CancellationTokenSource cts = new CancellationTokenSource(); 
cts.CancelAfter(TimeSpan.FromMilliseconds(300)); 

try 
{ 
    int bytesRead = 
     await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null) 
           .HandleCancellation(cts.Token); 
} 
catch (OperationCanceledException) 
{ 
    // Operation took longer than 300ms, and was treated as cancelled. 
} 
+0

Funziona ma sii consapevole che l'IO causerà comunque effetti collaterali e renderà indefinita la posizione del flusso. Il flusso diventa inutilizzabile. Questo in realtà non evita la necessità di aprire un nuovo stream. – usr

+0

Anche se questo è un metodo molto carino, non sono sicuro che la risposta sia la domanda dell'utente. Innanzitutto, il binding di un timeout hardcoded a un'operazione IO non è l'approccio corretto, poiché le prestazioni IO dipendono dall'hardware. Il metodo di estensione è anche buono, ma non gestisce le risorse di I/O in modo pulito, come indica correttamente @usr. –

+0

Il timeout era solo un esempio per dimostrare l'uso del metodo di estensione. @usr è corretto, sebbene la limitazione derivi dall'incapacità della classe 'Stream' di gestire letture concorrenti. Il metodo di estensione funzionerebbe senza problemi su classi che possono eseguire contemporaneamente operazioni asincrone. – Douglas

Problemi correlati