2016-01-26 24 views
48

ho bisogno di modificare un programma esistente e contiene codice seguente:Async attendono in LINQ selezionare

var inputs = events.Select(async ev => await ProcessEventAsync(ev)) 
        .Select(t => t.Result) 
        .Where(i => i != null) 
        .ToList(); 

Ma questo sembra molto strano per me, prima di tutto l'uso di async e await nella select. Secondo this answer di Stephen Cleary dovrei essere in grado di lasciar perdere.

Quindi il secondo Select che seleziona il risultato. Questo non significa che l'attività non è asincrona e viene eseguita in modo sincrono (così tanto sforzo per nulla), o l'attività verrà eseguita in modo asincrono e quando viene eseguita viene eseguito il resto della query?

Devo scrivere il codice precedente come segue in base al another answer by Stephen Cleary:

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))); 
var inputs = tasks.Where(result => result != null).ToList(); 

ed è completamente lo stesso come questo?

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)))) 
             .Where(result => result != null).ToList(); 

mentre sto lavorando su questo progetto mi piacerebbe cambiare il primo esempio di codice, ma io non sono troppo appassionato di cambiare (apparantly di lavoro) del codice asincrono. Forse mi sto solo preoccupando di nulla e tutti e 3 i campioni di codice fanno esattamente la stessa cosa?

ProcessEventsAsync assomiglia a questo:

async Task<InputResult> ProcessEventAsync(InputEvent ev) {...} 
+0

Qual è il tipo di reso di ProceesEventAsync? – tede24

+0

@ tede24 È 'Task ' con 'InputResult' che è una classe personalizzata. –

+0

Secondo me le tue versioni sono molto più facili da leggere. Tuttavia, ci si è dimenticati di selezionare i risultati dalle attività prima di 'Dove'. – Max

risposta

48
var inputs = events.Select(async ev => await ProcessEventAsync(ev)) 
        .Select(t => t.Result) 
        .Where(i => i != null) 
        .ToList(); 

ma questo sembra molto strano per me, prima di tutto l'uso di async e attendere nella selezione. Secondo questa risposta di Stephen Cleary dovrei riuscire a lasciar perdere.

La chiamata a Select è valida. Queste due linee sono essenzialmente identici:

events.Select(async ev => await ProcessEventAsync(ev)) 
events.Select(ev => ProcessEventAsync(ev)) 

(C'è una differenza minore quanto riguarda come un'eccezione sincrona sarebbe stato gettato dalla ProcessEventAsync, ma nel contesto di questo codice non importa affatto.)

Quindi il secondo Seleziona che seleziona il risultato. Questo non significa che l'attività non è asincrona e viene eseguita in modo sincrono (così tanto sforzo per nulla), o l'attività verrà eseguita in modo asincrono e quando viene eseguita viene eseguito il resto della query?

Significa che la query sta bloccando. Quindi non è veramente asincrono.

scomponendola:

var inputs = events.Select(async ev => await ProcessEventAsync(ev)) 

verranno dapprima iniziare un'operazione asincrona per ciascun evento. Allora questa linea:

    .Select(t => t.Result) 

sarà attendere che tali operazioni per completare uno alla volta (prima si attende per il funzionamento del primo evento, quindi il prossimo, quindi il prossimo, ecc).

Questa è la parte che non mi interessa, perché blocca e inoltre avvolgere eventuali eccezioni in AggregateException.

ed è completamente lo stesso come questo?

var tasks = await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev))); 
var inputs = tasks.Where(result => result != null).ToList(); 

var inputs = (await Task.WhenAll(events.Select(ev => ProcessEventAsync(ev)))) 
             .Where(result => result != null).ToList(); 

Sì, questi due esempi sono equivalenti. Entrambi iniziano tutte le operazioni asincrone (events.Select(...)), quindi attendono in modo asincrono tutte le operazioni da completare in qualsiasi ordine (await Task.WhenAll(...)), quindi proseguono con il resto del lavoro (Where...).

Entrambi questi esempi sono diversi dal codice originale. Il codice originale sta bloccando e avvolgerà le eccezioni in AggregateException.

+0

Cheers per averlo chiarito! Quindi, invece delle eccezioni racchiuse in un 'AggregateException' avrei ottenuto più eccezioni separate nel secondo codice? –

+1

@AlexanderDerck: No, sia nel vecchio che nel nuovo codice, verrà sollevata solo la prima eccezione. Ma con 'Result' verrebbe inserito in' AggregateException'. –

12

Codice esistente funziona, ma sta bloccando il filo.

.Select(async ev => await ProcessEventAsync(ev)) 

crea un nuovo compito per ogni evento, ma

.Select(t => t.Result) 

blocca il thread in attesa per ogni nuova attività alla fine.

D'altra parte il codice produce lo stesso risultato ma mantiene asincrono.

Solo un commento sul tuo primo codice. Questa riga

var tasks = await Task.WhenAll(events... 

produrrà un singolo Task in modo che la variabile debba essere denominata in singolare.

Infine l'ultima codice di fare lo stesso, ma è più succinto

Per riferimento: Task.Wait/Task.WhenAll

+0

Quindi il primo blocco di codice viene infatti eseguito in modo sincrono? –

+1

Sì, poiché l'accesso a Result produce un'attesa che blocca il thread. Nell'altra mano Quando produce una nuova attività che puoi attendere. – tede24

+1

Tornando a questa domanda e osservando la tua osservazione sul nome della variabile 'tasks', hai perfettamente ragione. Scelta orribile, non sono nemmeno compiti in quanto vengono attesi subito. Lascerò la domanda come se fosse –

2

Con gli attuali metodi disponibili in LINQ sembra abbastanza brutto:

 var tasks = items.Select(
      async item => new 
      { 
       Item = item, 
       IsValid = await IsValid(item) 
      }); 
     var tuples = await Task.WhenAll(tasks); 
     var validItems = tuples 
      .Where(p => p.IsValid) 
      .Select(p => p.Item) 
      .ToList(); 

Speriamo seguenti versioni di .NET si presenti con più elegante utensili per gestire collezioni di compiti e mansioni delle collezioni.