Chaque fois que vous devez effectuer une action sur un serveur distant, votre programme génère la requête, l'envoie, puis attend une réponse. Je vais utiliser SaveChanges()
et SaveChangesAsync()
comme exemple, mais il en va de même pour Find()
et FindAsync()
.
Supposons que vous ayez une liste myList
de plus de 100 éléments que vous devez ajouter à votre base de données. Pour insérer cela, votre fonction ressemblerait à ceci:
using(var context = new MyEDM())
{
context.MyTable.AddRange(myList);
context.SaveChanges();
}
Commencez par créer une instance de MyEDM
, ajoutez la liste myList
à la table MyTable
, puis appelez SaveChanges()
pour conserver les modifications apportées à la base de données. Cela fonctionne comme vous le souhaitez, les enregistrements sont validés, mais votre programme ne peut rien faire d'autre tant que la validation n'est pas terminée. Cela peut prendre du temps en fonction de ce que vous vous engagez. Si vous validez des modifications dans les enregistrements, l'entité doit les valider un par un (une fois, une sauvegarde a pris 2 minutes pour les mises à jour)!
Pour résoudre ce problème, vous pouvez effectuer l'une des deux opérations suivantes. Le premier est que vous pouvez démarrer un nouveau fil pour gérer l'insert. Bien que cela libère le thread appelant pour continuer à s'exécuter, vous avez créé un nouveau thread qui va simplement rester là et attendre. Il n'y a pas besoin de cette surcharge, et c'est ce que le async await
modèle résout.
Pour les opérations d'E / S, await
devient rapidement votre meilleur ami. En prenant la section de code ci-dessus, nous pouvons la modifier pour qu'elle soit:
using(var context = new MyEDM())
{
Console.WriteLine("Save Starting");
context.MyTable.AddRange(myList);
await context.SaveChangesAsync();
Console.WriteLine("Save Complete");
}
C'est un tout petit changement, mais il y a des effets profonds sur l'efficacité et les performances de votre code. Alors que se passe-t-il? Le début du code est le même, vous créez une instance de MyEDM
et ajoutez votre myList
à MyTable
. Mais lorsque vous appelez await context.SaveChangesAsync()
, l'exécution du code revient à la fonction appelante! Ainsi, pendant que vous attendez que tous ces enregistrements soient validés, votre code peut continuer à s'exécuter. Dites que la fonction qui contenait le code ci-dessus avait la signature de public async Task SaveRecords(List<MyTable> saveList)
, la fonction appelante pourrait ressembler à ceci:
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");
}
Pourquoi vous auriez une fonction comme celle-ci, je ne sais pas, mais ce qu'elle produit montre comment async await
fonctionne. Voyons d'abord ce qui se passe.
L'exécution entre MyCallingFunction
, Function Starting
puis Save Starting
est écrite sur la console, puis la fonction SaveChangesAsync()
est appelée. À ce stade, l'exécution retourne MyCallingFunction
et entre dans la boucle for en écrivant «Continuer à exécuter» jusqu'à 1000 fois. Une fois SaveChangesAsync()
terminé, l'exécution revient à la SaveRecords
fonction, en écrivant Save Complete
dans la console. Une fois que tout est SaveRecords
terminé, l'exécution se poursuivra MyCallingFunction
comme elle l'était à la SaveChangesAsync()
fin. Confus? Voici un exemple de sortie:
Démarrage de la fonction
Enregistrer le départ
Continuer à exécuter!
Continuer à exécuter!
Continuer à exécuter!
Continuer à exécuter!
Continuer à exécuter!
....
Continuer à exécuter!
Sauvegardez terminé!
Continuer à exécuter!
Continuer à exécuter!
Continuer à exécuter!
....
Continuer à exécuter!
Fonction terminée!
Ou peut-être:
Démarrage de la fonction
Enregistrer le départ
Continuer à exécuter!
Continuer à exécuter!
Sauvegardez terminé!
Continuer à exécuter!
Continuer à exécuter!
Continuer à exécuter!
....
Continuer à exécuter!
Fonction terminée!
C'est la beauté de async await
, votre code peut continuer à s'exécuter pendant que vous attendez que quelque chose se termine. En réalité, vous auriez une fonction plus comme celle-ci que votre fonction d'appel:
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());
}
Ici, vous disposez de quatre fonctions de sauvegarde d'enregistrement différentes en même temps . MyCallingFunction
se terminera beaucoup plus rapidement async await
que si les SaveRecords
fonctions individuelles étaient appelées en série.
La seule chose que je n'ai pas encore abordée est le await
mot clé. Cela empêche la fonction en cours de s'exécuter jusqu'à ce que tout ce que Task
vous attendez soit terminé. Donc, dans le cas de l'original MyCallingFunction
, la ligne Function Complete
ne sera pas écrite sur la console tant que la SaveRecords
fonction ne sera pas terminée.
Pour faire court, si vous avez une option à utiliser async await
, vous devriez car cela augmentera considérablement les performances de votre application.