2016-02-22 22 views
5

Sto guardando una programmazione asincrona in C# e mi chiedevo quale sarebbe la differenza tra queste funzioni che fa la stessa identica cosa e sono tutte in attesa.Qual è la differenza tra questi metodi attendibili?

public Task<Bar> GetBar(string fooId) 
{ 
    return Task.Run(() => 
    { 
     var fooService = new FooService(); 
     var bar = fooService.GetBar(fooId); 
     return bar; 
    }); 
} 

public Task<Bar> GetBar(string fooId) 
{ 
    var fooService = new FooService(); 
    var bar = fooService.GetBar(fooId); 
    return Task.FromResult(bar) 
} 

public async Task<Bar> GetBar(string fooId) 
{ 
    return await Task.Run(() => 
    { 
     var fooService = new FooService(); 
     var bar = fooService.GetBar(fooId); 
     return bar; 
    }); 
} 

La mia ipotesi è che il primo è il modo corretto di fare le cose, e il codice non viene eseguito fino a quando si tenta di ottenere il risultato dalla Task restituito.

Nel secondo, il codice viene eseguito durante la chiamata e il risultato viene archiviato nell'attività restituita.

E il terzo è un po 'come il secondo? Il codice viene eseguito su chiamata e il risultato di Task.Run è di ritorno? Quale in questo caso questa funzione sarebbe una specie di stupido?

Ho ragione o sono lontano?

+1

Il primo viene eseguito in modo asincrono, il secondo no e blocca. Il risultato viene restituito in modo sincrono racchiuso in un'attività. Il terzo è quasi lo stesso del primo: "async/await" semplifica semplicemente l'awating, non rende nulla asincrono. Il terzo in realtà ha qualche cruft inutile perché il risultato non è effettivamente utilizzato all'interno della funzione –

+0

PS. Non sto postando questa risposta come risposta perché ci sono un sacco di domande simili –

+1

Nota: questo è "asincrono sulla sincronizzazione": il servizio di back-end ('GetBar') è fondamentalmente un'API sincrona, e lo stai esponendo tramite un shim 'async'. Il * solo * motivo per farlo è per cose come liberare l'interfaccia utente per dipingere. Non raggiunge il set più ampio di obiettivi 'async': hai ancora ** un ** thread sat bloccato sul risultato - semplicemente non è il * thread * originale. –

risposta

6

Nessuna di queste implementazioni del metodo ha senso. Tutto quello che stai facendo è eliminare il lavoro di blocco sul pool di thread (o peggio, eseguirlo in modo sincrono e avvolgere il risultato in un'istanza Task<Bar>). Quello che dovresti fare è esporre l'API sincrona e lasciare che i chiamanti decidano come chiamarlo. Se vogliono o meno usare Task.Run, è a loro.

Detto questo, qui ci sono le differenze:

# 1

La prima variante (che restituisce un Task<Bar> creato tramite Task.Run direttamente) è il "più puro", anche se non fa molto senso da una prospettiva API. Stai consentendo a Task.Run di pianificare il lavoro specificato nel pool di thread e restituire il Task<Bar> che rappresenta il completamento dell'operazione asincrona al chiamante.

# 2

Il secondo metodo (che utilizza Task.FromResult) non è asincrona. Esegue in modo sincrono, proprio come una normale chiamata di metodo. Il risultato è semplicemente racchiuso in un'istanza completata Task<Bar>.

# 3

Questa è una versione più complicata della prima. Stai ottenendo un risultato simile a quello che fa # 1, ma con un extra, inutile e persino un po 'pericoloso await. Questo vale la pena guardare più in dettaglio.

async/await è grande per concatenamento operazioni asincrone tramite la combinazione di molteplici Task s rappresentano lavoro asincrono in una singola unità (Task). Ti aiuta a far accadere le cose nell'ordine giusto, ti offre un flusso di controllo ricco tra le tue operazioni asincrone e assicura che le cose accadano nel thread giusto.

Nessuno dei precedenti, tuttavia, è di alcun beneficio nel tuo scenario, perché hai solo uno Task. Pertanto, non è necessario fare in modo che il compilatore generi una macchina a stati solo per ottenere ciò che già fa Task.Run.

Un metodo mal progettato async può anche essere pericoloso. Non utilizzando ConfigureAwait(false) su await e Task si sta inavvertitamente introducendo una cattura SynchronizationContext, uccidendo le prestazioni e introducendo un rischio di deadlock senza alcun beneficio.

Se il chiamante decide di bloccare sul Task<Bar> in un ambiente che ha un SynchronizationContext (cioè le forme Win, WPF e possibilmente ASP.NET) via GetBar(fooId).Wait() o GetBar(fooId).Result, che riceveranno una situazione di stallo per le ragioni discusse here.

+0

Questo era solo un esempio e probabilmente sarebbe stato eseguito più codice in un Task.Run(). Se ho interpretato l'articolo che si riferiva al deadlock, apparirebbe solo se lo facessi: var bar = GetBar (fooId); bar.Result; In caso affermativo: var bar = attendi GetBar (fooId); in un metodo asincrono, non si verificherà? – Gralov

+1

@Gralov, corretto, 'attendere un' Task 'prodotto dai metodi 1 e 3 va bene. 'attendere un' Task 'prodotto dal metodo # 2 è anche * tecnicamente * possibile, ma non sensato affatto, poiché la maggior parte di esso verrà comunque eseguito in modo sincrono, anche quando atteso (in modo da ottenere tutte le spese generali relative alle prestazioni associate con 'async', e nessuno dei vantaggi). La descrizione –

0

Ho letto da qualche parte su Stackoverflow in un commento la seguente analogia. Perché è in un commento non riesco a trovarlo facilmente, quindi nessun collegamento ad esso.

Supponiamo di dover fare colazione. Fai bollire delle uova e tosta un po 'di pane.

Se si inizia a bollire le uova, poi da qualche parte nel sottoprogramma "far bollire delle uova" si dovrà aspettare fino a quando le uova sono bolliti

sincrono sarebbe di attendere fino a quando le uova sono finiti bollente prima di iniziare subroutine "Pane tostato".

Tuttavia sarebbe più efficiente se mentre le uova vengono bollite, non si aspetta, ma si inizia a tostare le uova. Quindi attendi che uno di loro finisca e continui il processo "Boil Eggs" o "Toast Bread" a seconda di cosa finisce prima. Questo è asincrono ma non simultaneo. È ancora una persona che sta facendo tutto.

Un terzo metodo sarebbe quello di assumere un cuoco che bolle uova mentre si tosta il pane. Questo è davvero concomitante: due persone stanno facendo qualcosa. Se sei davvero ricco, puoi anche noleggiare un tostapane, mentre leggi il giornale, ma ciao, non viviamo tutti in Downton Abbey ;-)

Torna alla tua domanda.

Nr 2: sincrono: il thread principale fa tutto il lavoro. Questo thread ritorna dopo che l'uovo è stato bollito prima che il chiamante possa fare qualcos'altro.

Nr 1 non è dichiarato asincrono. Ciò significa che, sebbene inizi un altro thread che svolgerà il lavoro, il tuo interlocutore non può continuare a fare qualcos'altro, e sebbene tu possa, non lo fai, devi solo aspettare che l'uovo sia bollito.

La terza procedura è dichiarata asincrona. Ciò significa che non appena inizia l'attesa dell'uovo, il chiamante può fare qualcos'altro, come tostare il pane. Nota che questo funziona tutto il lavoro è fatto da un thread.

Se il chiamante aspetterebbe non fare altro che attendere, non sarebbe molto utile, a meno che il chiamante non sia dichiarato asincrono. Ciò darebbe al chiamante del chiamante l'opportunità di fare qualcos'altro.

In genere quando si utilizza async-aspettano correttamente, viene visualizzato il seguente: - Ogni funzione dichiarata asincrone ritorna Task invece di vuoto, e Task < TResult> al posto di TResult - C'è una sola eccezione: il gestore di eventi restituisce vuoto invece di Attività. - Ogni funzione che richiama una funzione asincrona dovrebbe essere dichiarata asincrona, altrimenti usare async-await non è molto utile - Dopo aver chiamato un metodo asincrono è possibile iniziare a tostare il pane mentre l'uovo viene bollito.Quando il pane è tostato, puoi aspettare l'uovo, o puoi controllare se l'uovo è pronto durante la tostatura, o forse il più efficiente sarebbe attendere Task.WhenAny, per continuare a terminare l'uovo o il toast o attendere Task. QuandoTutto quando non hai nulla di utile da fare finchè non entrambi sono finiti.

Spero che questa analogia aiuti

+0

# 1 è errata. Quella variante fa la * quantità minima * di lavoro in modo sincrono (sul thread "principale") prima di restituire il 'Task ' al chiamante (a questo punto il thread "principale" è libero di fare qualcos'altro). Nella # 3 la sezione del metodo che è garantito per essere eseguita in modo sincrono è leggermente più grassa: si avrà ancora la chiamata a 'Task.Run' che si verifica nel thread" principale ", ma è * anche * seguita da tutte le attendibili- lavoro correlato ('GetAwaiter', check' IsCompleted', schedule continuation), anche sul thread "main", prima che 'Task ' sia finalmente restituito al chiamante. –

+0

L'analogia della colazione suggerisce l'introduzione del multi-threading, che non è lo scopo (primario) di attesa asincrona. – MEMark

Problemi correlati