Szukałem różnic między 2 parami powyżej, ale nie znalazłem żadnych artykułów wyjaśniających to jasno, a także kiedy użyć jednej lub drugiej.
Jaka jest różnica między SaveChanges()
i SaveChangesAsync()
?
A między Find()
a FindAsync()
?
Po stronie serwera, gdy używamy Async
metod, musimy również dodać await
. Dlatego nie sądzę, że jest asynchroniczny po stronie serwera.
Czy pomaga to tylko w zapobieganiu blokowaniu interfejsu użytkownika w przeglądarce po stronie klienta? A może są między nimi jakieś wady i zalety?
c#
entity-framework
async-await
Hien Tran
źródło
źródło
Odpowiedzi:
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()
iSaveChangesAsync()
jako przykładu, ale to samo dotyczyFind()
iFindAsync()
.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 tabeliMyTable
, 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
doMyTable
. Ale kiedy dzwoniszawait 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 podpispublic 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ępnieSave Starting
zostaje zapisane w konsoli, a następnieSaveChangesAsync()
wywoływana jest funkcja . W tym momencie wykonanie powracaMyCallingFunction
i wchodzi do pętli for, zapisując „Kontynuacja wykonywania” do 1000 razy. PoSaveChangesAsync()
zakończeniu wykonanie wraca doSaveRecords
funkcji, piszącSave Complete
do konsoli. Gdy wszystko zostanieSaveRecords
ukończone, wykonywanie będzie kontynuowaneMyCallingFunction
tak, jak było poSaveChangesAsync()
zakończeniu. Zmieszany? Oto przykładowe dane wyjściowe:Albo może:
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ólneSaveRecords
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 czasuTask
zakończenia tego , na co czekasz. Tak więc w przypadku oryginałuMyCallingFunction
liniaFunction Complete
nie zostanie zapisana na konsoli, dopókiSaveRecords
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.źródło
await
jednak używasz , nawet jeśli nie musisz robić nic więcej po wywołaniu SaveChanges, ASP powie "aha, ten wątek powrócił w oczekiwaniu na operację asynchroniczną, co oznacza, że mogę pozwolić temu wątkowi obsłużyć w międzyczasie inne żądanie ! ” Dzięki temu Twoja aplikacja znacznie lepiej skaluje się w poziomie.await
for,SaveChangesAsync
ponieważ EF nie obsługuje wielu zapisów w tym samym czasie. docs.microsoft.com/en-us/ef/core/saving/async Ponadto korzystanie z tych metod asynchronicznych ma naprawdę dużą zaletę. Na przykład możesz nadal otrzymywać inne żądania w swoim webApi podczas zapisywania danych lub wykonywania wielu operacji sutuff, lub poprawić wrażenia użytkownika, nie blokując interfejsu, gdy jesteś w aplikacji komputerowej.Moje pozostałe wyjaśnienie będzie oparte na następującym fragmencie kodu.
using System; using System.Threading; using System.Threading.Tasks; using static System.Console; public static class Program { const int N = 20; static readonly object obj = new object(); static int counter; public static void Job(ConsoleColor color, int multiplier = 1) { for (long i = 0; i < N * multiplier; i++) { lock (obj) { counter++; ForegroundColor = color; Write($"{Thread.CurrentThread.ManagedThreadId}"); if (counter % N == 0) WriteLine(); ResetColor(); } Thread.Sleep(N); } } static async Task JobAsync() { // intentionally removed } public static async Task Main() { // intentionally removed } }
Przypadek 1
static async Task JobAsync() { Task t = Task.Run(() => Job(ConsoleColor.Red, 1)); Job(ConsoleColor.Green, 2); await t; Job(ConsoleColor.Blue, 1); } public static async Task Main() { Task t = JobAsync(); Job(ConsoleColor.White, 1); await t; }
Uwagi: Ponieważ synchroniczna część (zielona)
JobAsync
wiruje dłużej niż zadaniet
(kolor czerwony), to zadaniet
jest już zakończone w punkcieawait t
. W rezultacie kontynuacja (niebieska) przebiega na tej samej nitce co zielona. Synchroniczna częśćMain
(biała) obróci się, gdy zielona skończy się kręcić. Dlatego część synchroniczna w metodzie asynchronicznej jest problematyczna.Przypadek 2
static async Task JobAsync() { Task t = Task.Run(() => Job(ConsoleColor.Red, 2)); Job(ConsoleColor.Green, 1); await t; Job(ConsoleColor.Blue, 1); } public static async Task Main() { Task t = JobAsync(); Job(ConsoleColor.White, 1); await t; }
Uwagi: Ten przypadek jest odwrotny do pierwszego przypadku. Synchroniczna część (kolor zielony)
JobAsync
kręci się krócej niż zadaniet
(kolor czerwony) to zadaniet
nie zostało ukończone w punkcieawait t
. W rezultacie kontynuacja (niebieska) przebiega na innym wątku niż zielony. Synchroniczna częśćMain
(biała) nadal obraca się po tym, jak zielona kończy się obracać.Przypadek 3
static async Task JobAsync() { Task t = Task.Run(() => Job(ConsoleColor.Red, 1)); await t; Job(ConsoleColor.Green, 1); Job(ConsoleColor.Blue, 1); } public static async Task Main() { Task t = JobAsync(); Job(ConsoleColor.White, 1); await t; }
Uwagi: Ten przypadek rozwiąże problem z poprzednich przypadków dotyczący części synchronicznej w metodzie asynchronicznej. Zadanie
t
jest natychmiast oczekiwane. W rezultacie kontynuacja (niebieska) przebiega na innym wątku niż zielony. Synchroniczna częśćMain
(biała) obróci się natychmiast równolegle doJobAsync
.Jeśli chcesz dodać inne sprawy, możesz je edytować.
źródło
To stwierdzenie jest nieprawidłowe:
Nie trzeba dodawać „await”,
await
jest to po prostu wygodne słowo kluczowe w języku C #, które umożliwia napisanie większej liczby wierszy kodu po wywołaniu, a pozostałe wiersze zostaną wykonane dopiero po zakończeniu operacji zapisywania. Ale jak zauważyłeś, możesz to osiągnąć po prostu dzwoniącSaveChanges
zamiastSaveChangesAsync
.Ale zasadniczo wywołanie asynchroniczne to znacznie więcej. Pomysł jest taki, że jeśli jest inna praca, którą możesz wykonać (na serwerze) w trakcie operacji zapisywania, powinieneś użyć
SaveChangesAsync
. Nie używaj „await”. Po prostu zadzwońSaveChangesAsync
, a następnie kontynuuj równolegle wykonywanie innych czynności. Obejmuje to potencjalnie, w aplikacji internetowej, zwrócenie odpowiedzi do klienta, nawet przed zakończeniem zapisywania. Ale oczywiście nadal będziesz chciał sprawdzić ostateczny wynik zapisywania, aby w przypadku niepowodzenia możesz przekazać to użytkownikowi lub w jakiś sposób zarejestrować.źródło