2015-05-05 15 views
49

Ho cercato le differenze tra 2 coppie sopra ma non ho trovato nessun articolo che spieghi chiaramente su di esso e quando utilizzare uno o l'altro.Entity Framework SaveChanges() vs. SaveChangesAsync() e Find() vs. FindAsync()

Quindi qual è la differenza tra SaveChanges() e SaveChangesAsync()?
E tra Find() e FindAsync()?

Sul lato server, quando si utilizzano i metodi Async, è inoltre necessario aggiungere await. Quindi, non penso che sia asincrono sul lato server.

Aiuta solo a impedire il blocco dell'interfaccia utente sul browser lato client? O ci sono pro e contro tra loro?

+1

async è molto, * molto * più di quello che impedisce al thread dell'interfaccia utente del client di bloccare nelle applicazioni client. Sono sicuro che ci sarà una risposta esperta a breve. – jdphenix

risposta

98

Ogni volta che è necessario eseguire un'azione su un server remoto, il programma genera la richiesta, la invia, quindi attende una risposta. Userò SaveChanges() e SaveChangesAsync() come esempio, ma lo stesso vale per Find() e FindAsync().

Supponiamo di avere una lista myList di 100+ elementi che è necessario aggiungere al proprio database. Per inserire questo, la funzione potrebbe essere simile così:

using(var context = new MyEDM()) 
{ 
    context.MyTable.AddRange(myList); 
    context.SaveChanges(); 
} 

Per prima cosa è creare e istanza di MyEDM, aggiungere l'elenco myList al tavolo MyTable, quindi chiamare SaveChanges() a persistere le modifiche al database. Funziona come vuoi, i record vengono attivati, ma il tuo programma non può fare nient'altro fino a quando il commit non finisce. Questo può richiedere molto tempo a seconda di ciò che stai commettendo. Se si stanno commettendo modifiche ai record, l'entità deve impegnarli uno alla volta (una volta ho avuto un salvataggio in 2 minuti per gli aggiornamenti)!

Per risolvere questo problema, è possibile fare una delle due cose. Il primo è che puoi avviare una nuova discussione per gestire l'inserimento. Mentre questo consente di liberare il thread chiamante per continuare l'esecuzione, hai creato un nuovo thread che si sta semplicemente seduto lì e aspetta. Non è necessario per questo sovraccarico, e questo è ciò che risolve il modello async await.

Per le operazioni di I/O, await diventa rapidamente il vostro migliore amico.Prendendo la sezione di codice da sopra, possiamo modificare per essere:

using(var context = new MyEDM()) 
{ 
    Console.WriteLine("Save Starting"); 
    context.MyTable.AddRange(myList); 
    await context.SaveChangesAsync(); 
    Console.WriteLine("Save Complete"); 
} 

E 'un piccolo cambiamento, ma ci sono effetti profondi sulla efficienza e le prestazioni del vostro codice. Quindi cosa succede? L'inizio del codice è lo stesso, si crea un'istanza di MyEDM e si aggiunge myList a MyTable. Ma quando chiami await context.SaveChangesAsync(), l'esecuzione del codice torna alla funzione di chiamata! Quindi, mentre stai aspettando che tutti quei record vengano confermati, il tuo codice può continuare a essere eseguito. Di 'la funzione che conteneva il codice di cui sopra ha avuto la firma del public async Task SaveRecords(List<MyTable> saveList), funzione chiamante potrebbe assomigliare a questo:

public async Task MyCallingFunction() 
{ 
    Console.WriteLine("Function Starting"); 
    Task saveTask = SaveRecords(GenerateNewRecords()); 

    for(int i = 0; i < 1000; i++){ 
     Console.WriteLine("Continuing to execute!"); 
    } 

    await saveTask; 
    Console.Log("Function Complete"); 
} 

Perché si avrebbe una funzione come questa, io non lo so, ma quello che emette mostra come async await funziona. Prima lascia andare su quello che succede.

L'esecuzione entra in MyCallingFunction, Function Starting quindi Save Starting viene scritto nella console, quindi viene richiamata la funzione SaveChangesAsync(). A questo punto, l'esecuzione ritorna a MyCallingFunction e inserisce il ciclo for che scrive 'Continuazione per l'esecuzione' fino a 1000 volte. Al termine di SaveChangesAsync(), l'esecuzione torna alla funzione SaveRecords, scrivendo Save Complete nella console. Una volta completato il tutto in SaveRecords, l'esecuzione continuerà nel MyCallingFunction giusto quando è stato completato lo SaveChangesAsync(). Confuso? Ecco un esempio di output:

 
Function Starting 
Save Starting 
Continuing to execute! 
Continuing to execute! 
Continuing to execute! 
Continuing to execute! 
Continuing to execute! 
.... 
Continuing to execute! 
Save Complete! 
Continuing to execute! 
Continuing to execute! 
Continuing to execute! 
.... 
Continuing to execute! 
Function Complete! 

O forse:

 
Function Starting 
Save Starting 
Continuing to execute! 
Continuing to execute! 
Save Complete! 
Continuing to execute! 
Continuing to execute! 
Continuing to execute! 
.... 
Continuing to execute! 
Function Complete! 

Questa è la bellezza della async await, il codice può continuare a funzionare mentre si è in attesa di qualcosa da finire. In realtà, si dovrebbe avere una funzione più simile a questo come la vostra funzione di chiamata:

public async Task MyCallingFunction() 
{ 
    List<Task> myTasks = new List<Task>(); 
    myTasks.Add(SaveRecords(GenerateNewRecords())); 
    myTasks.Add(SaveRecords2(GenerateNewRecords2())); 
    myTasks.Add(SaveRecords3(GenerateNewRecords3())); 
    myTasks.Add(SaveRecords4(GenerateNewRecords4())); 

    await Task.WhenAll(myTasks.ToArray()); 
} 

Qui, avere quattro differenti salvare funzioni di registrazione che vanno allo stesso tempo. MyCallingFunction si completerà molto più rapidamente utilizzando async await se poi le singole funzioni SaveRecords sono state chiamate in serie.

L'unica cosa che non ho ancora toccato è la parola chiave await. Ciò che fa è interrompere l'esecuzione della funzione corrente fino al completamento di Task che si è in attesa. Pertanto, nel caso dell'originale MyCallingFunction, la riga Function Complete non verrà scritta sulla console fino al termine della funzione SaveRecords.

Per farla breve, se si dispone di un'opzione per utilizzare async await, si dovrebbe aumentare notevolmente le prestazioni della vostra applicazione.

+3

spiegazione molto bella, grazie! – renakre

+3

Il 99% delle volte devo ancora attendere i valori da ricevere dal database prima che possa continuare. Dovrei ancora usare async? Async consente a 100 persone di connettersi al mio sito web in modo asincrono? Se non uso async significa che tutti i 100 utenti devono attendere nella riga 1 alla volta? – MIKE

+1

Vale la pena notare: la generazione di una nuova discussione dal pool di thread rende ASP un panda triste poiché in pratica ruba un thread da ASP (ovvero non può gestire altre richieste o eseguire alcuna operazione poiché è bloccato in una chiamata di blocco). Se si utilizza 'await', tuttavia, anche se non è necessario fare altro dopo la chiamata a SaveChanges, ASP dirà" aha, questo thread è tornato in attesa di un'operazione asincrona, questo significa che posso lasciare che questo thread gestisca qualche altra richiesta nel frattempo!" Questo rende la tua app in scala orizzontale molto meglio. – kai

3

Questa affermazione non è corretta:

Sul lato server, quando usiamo i metodi asincroni, abbiamo anche bisogno di aggiungere attendono.

Non è necessario aggiungere "attendere". "await" è semplicemente una comoda parola chiave in C# che consente di scrivere più righe di codice dopo la chiamata e quelle altre linee verranno eseguite solo dopo il completamento dell'operazione di salvataggio. Ma come hai sottolineato, puoi farlo semplicemente chiamando SaveChanges invece di SaveChangesAsync.

Ma fondamentalmente, una chiamata asincrona è molto più di questo. L'idea è che se c'è un altro lavoro che puoi fare (sul server) mentre l'operazione di salvataggio è in corso, dovresti usare SaveChangesAsync. Non usare "attendere". Basta chiamare SaveChangesAsync e quindi continuare a fare altre cose in parallelo. Ciò include potenzialmente, in un'app Web, la restituzione di una risposta al client ancor prima che il salvataggio sia stato completato. Ma, naturalmente, vorrete comunque controllare il risultato finale del salvataggio in modo che, in caso di fallimento, sia possibile comunicarlo all'utente o registrarlo in qualche modo.

+3

In realtà si desidera attendere queste chiamate altrimenti è possibile eseguire query o salvare contemporaneamente i dati utilizzando la stessa istanza di DbContext e DbContext non è thread-safe. Oltre a ciò, è facile gestire le eccezioni. Senza attendere si dovrebbe archiviare l'attività e verificare se è in errore, ma senza sapere quando l'attività completata non si saprebbe quando controllare, a meno che non si usi '. Continua', che richiede molto più pensiero che attendere. – Pawel

+14

Questa risposta è ingannevole. Chiamare un metodo asincrono senza attendere lo rende un "fuoco e dimentica."Il metodo si spegne e probabilmente si completerà prima o poi, ma non saprai mai quando, e se lancia un'eccezione non ne sentirai mai parlare, non sei in grado di sincronizzarti con il suo completamento. Questo tipo di comportamento potenzialmente pericoloso dovrebbe essere scelto, non invocato con una regola semplice (e non corretta) come "attende sul client, non attende sul server." –

+1

Questa è una conoscenza molto utile che avevo letto nella documentazione, ma che non avevo davvero preso in considerazione. l'opzione per: 1. SaveChangesAsync() su "Fire and forget", come dice John Melville ... che mi è utile in alcuni casi: 2. Attendi SaveChangesAsync() su "Fuoco, torna al chiamante e quindi esegui un codice "post-save" dopo che il salvataggio è completato. Pezzo molto utile. Grazie. –

Problemi correlati