Perché dovresti mai "aspettare" un metodo e quindi interrogare immediatamente il suo valore di ritorno?

19

In questo articolo MSDN , viene fornito il seguente codice di esempio (leggermente modificato per brevità):

public async Task<ActionResult> Details(int? id)
{
    if (id == null)
    {
        return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
    }

    Department department = await db.Departments.FindAsync(id);

    if (department == null)
    {
        return HttpNotFound();
    }

    return View(department);
}

Il metodo FindAsync recupera un oggetto Department dal suo ID e restituisce un Task<Department> . Quindi il dipartimento è immediatamente controllato per vedere se è nullo. A quanto ho capito, chiedendo il valore del Task in questo modo, blocca l'esecuzione del codice fino a quando viene restituito il valore del metodo atteso, rendendo effettivamente questa una chiamata sincrona.

Perché mai dovresti farlo? Non sarebbe più semplice chiamare il metodo sincrono Find(id) , se hai intenzione di bloccare immediatamente comunque?

    
posta Robert Harvey 11.08.2016 - 21:59
fonte

5 risposte

20

As I understand it, asking for the Task's value in this manner will block code execution until the value from the awaited method is returned, effectively making this a synchronous call.

Non proprio.

Quando si chiama await db.Departments.FindAsync(id) , l'attività viene inviata e il thread corrente viene restituito al pool per essere utilizzato da altre operazioni. Il flusso di esecuzione è bloccato (come sarebbe a prescindere dall'uso di department subito dopo, se capisco le cose correttamente), ma il thread stesso è libero di essere usato da altre cose mentre aspetti che l'operazione venga completata off- macchina (e segnalata da un evento o una porta di completamento).

Se hai chiamato d.Departments.Find(id) , il thread si siede lì e aspetta la risposta, anche se la maggior parte dell'elaborazione viene eseguita sul DB.

Stai effettivamente liberando risorse della CPU quando il disco è limitato.

    
risposta data 11.08.2016 - 22:13
fonte
9

Odio davvero che nessuno degli esempi mostra come sia possibile attendere alcune righe prima di attendere l'attività. Considera questo.

Foo foo = await getFoo();
Bar bar = await getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(foo, bar);

Questo è il tipo di codice incoraggiato dagli esempi e hai ragione. Non ha molto senso in questo. Rende libero il thread principale per fare altre cose, come rispondere all'input dell'interfaccia utente, ma il vero potere di async / await è che posso facilmente continuare a fare altre cose mentre sto aspettando il completamento di un'attività potenzialmente a lungo termine. Il codice sopra "bloccherà" e attenderà di eseguire la linea di stampa finché non avremo trovato Foo & Bar. Non c'è bisogno di aspettare però. Possiamo processarlo mentre aspettiamo.

Task<Foo> foo = getFoo();
Task<Bar> bar = getBar();

Console.WriteLine(“Do some other stuff to prepare.”);

doStuff(await foo, await bar);

Ora, con il codice riscritto, non ci fermiamo e aspettiamo i nostri valori fino a quando non sarà necessario. Sono sempre alla ricerca di questo tipo di opportunità. Essere intelligenti quando attendiamo può portare a significativi miglioramenti delle prestazioni. Abbiamo più core in questi giorni, potrebbe anche usarli.

    
risposta data 04.08.2018 - 13:23
fonte
6

Quindi c'è di più che succede dietro le quinte qui. Async / Await è lo zucchero sintattico. Per prima cosa guarda la firma della funzione FindAsync. Restituisce un compito. Già stai vedendo la magia della parola chiave, essa disattiva quell'attività in un dipartimento.

La funzione di chiamata non blocca. Quello che succede è che l'assegnazione al dipartimento e tutto ciò che segue la parola chiave await viene inserito in una chiusura e per tutti gli scopi passati a Task.ContinueWith metodo (la funzione FindAsync viene eseguita automaticamente su un thread diverso).

Ovviamente c'è di più dietro le quinte perché l'operazione viene riportata al thread originale (quindi non devi più preoccuparti della sincronizzazione con l'interfaccia utente quando esegui un'operazione in background) e nel caso della funzione di chiamata essendo Async (ed essendo chiamato in modo asincrono) accade lo stesso stack.

Quindi, ciò che accade è che ottieni la magia delle operazioni Async, senza le insidie.

    
risposta data 11.08.2016 - 22:18
fonte
2

Mi piace pensare "asincrona" come un contratto, un contratto che dice "posso eseguire questo in modo asincrono se ne hai bisogno, ma puoi chiamarmi come qualsiasi altra funzione sincrona".

Significa che uno sviluppatore stava facendo delle funzioni e alcune decisioni di progettazione li hanno portati a fare / segnare una serie di funzioni come "asincrone". Il chiamante / consumatore delle funzioni è libero di usarle come decidono. Come dici tu puoi chiamare al momento giusto prima della chiamata alla funzione e attendere, in questo modo lo hai trattato come una funzione sincrona, ma se lo desideri, puoi chiamarlo senza attendere come

Task<Department> deptTask = db.Departments.FindAsync(id);

e dopo, per esempio, 10 linee verso il basso della funzione che chiami

Department d = await deptTask;

quindi trattandolo come una funzione asincrona.

È fino a te.

    
risposta data 04.08.2018 - 08:23
fonte
1

No, non sta tornando immediatamente. L'attesa rende il metodo chiamato asincrono. Quando viene chiamato FindAsync, il metodo Details viene restituito con un'attività che non è stata completata. Al termine di FindAsync, restituisce il risultato nella variabile dipartimento e riprende il resto del metodo Dettagli.

    
risposta data 11.08.2016 - 22:08
fonte

Leggi altre domande sui tag