Za każdym razem, gdy musisz wykonać jakąś akcję na serwerze zdalnym, twój program generuje żądanie, wysyła je, a następnie czeka na odpowiedź. Użyję SaveChanges()
i SaveChangesAsync()
jako przykładu, ale to samo dotyczy Find()
i FindAsync()
.
Załóżmy, że masz listę myList
ponad 100 elementów, które musisz dodać do swojej bazy danych. Aby to wstawić, twoja funkcja wyglądałaby mniej więcej tak:
using(var context = new MyEDM())
{
context.MyTable.AddRange(myList);
context.SaveChanges();
}
Najpierw tworzysz instancję MyEDM
, dodajesz listę myList
do tabeli MyTable
, a następnie wywołujesz, SaveChanges()
aby utrwalić zmiany w bazie danych. Działa tak, jak chcesz, rekordy są zatwierdzane, ale twój program nie może zrobić nic innego, dopóki zatwierdzanie nie zostanie zakończone. Może to zająć dużo czasu w zależności od tego, co robisz. Jeśli zatwierdzasz zmiany w rekordach, podmiot musi zatwierdzać je pojedynczo (kiedyś miałem zapis, który zajął 2 minuty na aktualizacje)!
Aby rozwiązać ten problem, możesz zrobić jedną z dwóch rzeczy. Po pierwsze, możesz uruchomić nowy gwint do obsługi wkładki. Chociaż zwolni to wątek wywołujący, aby kontynuować wykonywanie, utworzyłeś nowy wątek, który będzie tam siedział i czekał. Nie ma potrzeby tego narzutu i to właśnie async await
rozwiązuje wzór.
W przypadku operacji we / wy await
szybko staje się najlepszym przyjacielem. Biorąc sekcję kodu z góry, możemy zmodyfikować ją tak, aby była:
using(var context = new MyEDM())
{
Console.WriteLine("Save Starting");
context.MyTable.AddRange(myList);
await context.SaveChangesAsync();
Console.WriteLine("Save Complete");
}
To bardzo mała zmiana, ale ma głęboki wpływ na wydajność i wydajność Twojego kodu. Więc co się dzieje? Początek kodu jest taki sam, tworzysz instancję MyEDM
i dodajesz ją myList
do MyTable
. Ale kiedy dzwonisz await context.SaveChangesAsync()
, wykonanie kodu wraca do funkcji wywołującej! Więc gdy czekasz na zatwierdzenie wszystkich tych rekordów, twój kod może nadal być wykonywany. Powiedzmy, że funkcja, która zawiera powyższy kod ma podpis public async Task SaveRecords(List<MyTable> saveList)
, funkcja wywołująca mogłaby wyglądać tak:
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");
}
Dlaczego miałbyś mieć taką funkcję, nie wiem, ale to, co wyprowadza, pokazuje, jak async await
działa. Najpierw przyjrzyjmy się, co się dzieje.
Wykonywanie wchodzi MyCallingFunction
, Function Starting
następnie Save Starting
zostaje zapisane w konsoli, a następnie SaveChangesAsync()
wywoływana jest funkcja . W tym momencie wykonanie powraca MyCallingFunction
i wchodzi do pętli for, zapisując „Kontynuacja wykonywania” do 1000 razy. Po SaveChangesAsync()
zakończeniu wykonanie wraca do SaveRecords
funkcji, pisząc Save Complete
do konsoli. Gdy wszystko zostanie SaveRecords
ukończone, wykonywanie będzie kontynuowane MyCallingFunction
tak, jak było po SaveChangesAsync()
zakończeniu. Zmieszany? Oto przykładowe dane wyjściowe:
Uruchamianie funkcji
Zapisz Rozpoczynanie
Kontynuacja wykonywania!
Kontynuacja wykonywania!
Kontynuacja wykonywania!
Kontynuacja wykonywania!
Kontynuacja wykonywania!
....
Kontynuacja wykonywania!
Zapisz zakończone!
Kontynuacja wykonywania!
Kontynuacja wykonywania!
Kontynuacja wykonywania!
....
Kontynuacja wykonywania!
Funkcja zakończona!
Albo może:
Uruchamianie funkcji
Zapisz Rozpoczynanie
Kontynuacja wykonywania!
Kontynuacja wykonywania!
Zapisz zakończone!
Kontynuacja wykonywania!
Kontynuacja wykonywania!
Kontynuacja wykonywania!
....
Kontynuacja wykonywania!
Funkcja zakończona!
Na tym polega piękno async await
, Twój kod może nadal działać, gdy czekasz na zakończenie. W rzeczywistości jako funkcję wywołującą miałbyś funkcję podobną do tej:
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());
}
Tutaj masz cztery różne funkcje zapisu danych działające w tym samym czasie . MyCallingFunction
zakończy się dużo szybciej, async await
niż gdyby poszczególne SaveRecords
funkcje były wywoływane szeregowo.
Jedyną rzeczą, której jeszcze nie poruszyłem, jest await
słowo kluczowe. Powoduje to zatrzymanie wykonywania bieżącej funkcji do czasu Task
zakończenia tego , na co czekasz. Tak więc w przypadku oryginału MyCallingFunction
linia Function Complete
nie zostanie zapisana na konsoli, dopóki SaveRecords
funkcja nie zostanie zakończona.
Krótko mówiąc, jeśli masz możliwość użycia async await
, powinieneś, ponieważ znacznie zwiększy to wydajność Twojej aplikacji.