Я искал различия между двумя парами, указанными выше, но не нашел статей, четко объясняющих об этом, а также о том, когда использовать ту или другую.
Так в чем разница между SaveChanges()
и SaveChangesAsync()
?
А между Find()
и FindAsync()
?
На стороне сервера, когда мы используем Async
методы, нам также нужно добавить await
. Таким образом, я не думаю, что это асинхронно на стороне сервера.
Помогает ли это только предотвратить блокировку пользовательского интерфейса в браузере на стороне клиента? Или между ними есть какие-то плюсы и минусы?
c#
entity-framework
async-await
Hien Tran
источник
источник
Ответы:
Каждый раз, когда вам нужно выполнить действие на удаленном сервере, ваша программа генерирует запрос, отправляет его, а затем ожидает ответа. Я буду использовать
SaveChanges()
иSaveChangesAsync()
в качестве примера, но то же самое применимо кFind()
иFindAsync()
.Допустим, у вас есть список
myList
из более чем 100 элементов, которые нужно добавить в свою базу данных. Чтобы вставить это, ваша функция будет выглядеть примерно так:using(var context = new MyEDM()) { context.MyTable.AddRange(myList); context.SaveChanges(); }
Сначала вы создаете экземпляр
MyEDM
, добавляете списокmyList
в таблицуMyTable
, а затем вызываете,SaveChanges()
чтобы сохранить изменения в базе данных. Он работает так, как вы хотите, записи фиксируются, но ваша программа не может делать ничего другого, пока фиксация не завершится. Это может занять много времени в зависимости от того, что вы делаете. Если вы фиксируете изменения в записях, сущность должна фиксировать их по одному (у меня однажды было сохранение, занимающее 2 минуты для обновлений)!Чтобы решить эту проблему, вы можете сделать одно из двух. Во-первых, вы можете запустить новый поток для обработки вставки. Хотя это освободит вызывающий поток для продолжения выполнения, вы создали новый поток, который просто будет сидеть и ждать. В этих накладных расходах нет необходимости, и именно это
async await
решает шаблон.Для операций ввода-вывода
await
быстро становится вашим лучшим другом. Взяв фрагмент кода сверху, мы можем изменить его так:using(var context = new MyEDM()) { Console.WriteLine("Save Starting"); context.MyTable.AddRange(myList); await context.SaveChangesAsync(); Console.WriteLine("Save Complete"); }
Это очень небольшое изменение, но оно оказывает глубокое влияние на эффективность и производительность вашего кода. Так что же происходит? Начало кода такое же, вы создаете экземпляр
MyEDM
и добавляете свойmyList
вMyTable
. Но когда вы вызываетеawait context.SaveChangesAsync()
, выполнение кода возвращается к вызывающей функции! Итак, пока вы ждете фиксации всех этих записей, ваш код может продолжать выполнение. Скажем, функция, содержащая приведенный выше код, имела подписьpublic async Task SaveRecords(List<MyTable> saveList)
, вызывающая функция могла бы выглядеть так: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"); }
Зачем нужна такая функция, я не знаю, но то, что она выводит, показывает, как она
async await
работает. Сначала давайте разберемся, что происходит.Начинается выполнение
MyCallingFunction
,Function Starting
затемSave Starting
записывается в консоль, после чегоSaveChangesAsync()
вызывается функция . На этом этапе выполнение возвращается кMyCallingFunction
циклу for и переходит к нему, записывая «Continuing to Execute» до 1000 раз. ПоSaveChangesAsync()
завершении выполнение возвращается кSaveRecords
функции, записываяSave Complete
ее в консоль. Как только все будетSaveRecords
завершено, выполнение продолжится такMyCallingFunction
, как было доSaveChangesAsync()
завершения. Смущенный? Вот пример вывода:Или, может быть:
В этом прелесть того
async await
, что ваш код может продолжать работать, пока вы ждете, пока что-то завершится. На самом деле у вас будет функция, более похожая на эту, как ваша вызывающая функция: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()); }
Здесь у вас есть четыре различных сохранить запись функций , идущих одновременно .
MyCallingFunction
будет выполняться намного быстрее,async await
чем если бы отдельныеSaveRecords
функции вызывались последовательно.Единственное, что я еще не затронул, - это
await
ключевое слово. Это останавливает выполнение текущей функции до тех пор, пока все, чтоTask
вы ожидаете, не завершится. Таким образом, в случае оригиналаMyCallingFunction
строкаFunction Complete
не будет записана в консоль до завершенияSaveRecords
функции.Короче говоря, если у вас есть возможность использовать
async await
, вы должны, поскольку это значительно повысит производительность вашего приложения.источник
await
Однако, если вы используете , даже если ВАМ не нужно больше ничего делать после вызова SaveChanges, ASP скажет: «Ага, этот поток вернулся в ожидании асинхронной операции, это означает, что я могу позволить этому потоку обрабатывать другой запрос тем временем. ! " Это значительно улучшает масштабирование вашего приложения по горизонтали.await
дляSaveChangesAsync
поскольку EF не поддерживает несколько экономит одновременно. docs.microsoft.com/en-us/ef/core/saving/async Кроме того, использование этих асинхронных методов дает большое преимущество. Например, вы можете продолжать получать другие запросы в своем webApi при сохранении данных или выполнении большого количества операций, или улучшить пользовательский интерфейс, не замораживая интерфейс, когда вы находитесь в настольном приложении.Мое оставшееся объяснение будет основано на следующем фрагменте кода.
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 } }
Случай 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; }
Примечания: Поскольку синхронная часть (зеленый)
JobAsync
вращается дольше, чем задачаt
(красный), то задачаt
уже завершена на моментawait t
. В результате продолжение (синее) выполняется в том же потоке, что и зеленое. Синхронная частьMain
(белая) будет вращаться после того, как зеленая закончит вращение. Поэтому синхронная часть в асинхронном методе проблематична.Случай 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; }
Примечания: Этот случай противоположен первому. Синхронная часть (зеленый цвет)
JobAsync
вращается короче, чем задачаt
(красный цвет), тогда задачаt
не была завершена на моментawait t
. В результате продолжение (синее) выполняется в другом потоке, что и зеленый. Синхронная частьMain
(белая) продолжает вращаться после того, как зеленая закончила вращение.Случай 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; }
Замечания: Этот случай решит проблему в предыдущих случаях о синхронной части в асинхронном методе. Задача
t
сразу же ожидается. В результате продолжение (синее) выполняется в другом потоке, что и зеленый. Синхронная частьMain
(белая) будет вращаться сразу параллельноJobAsync
.Если вы хотите добавить другие случаи, не стесняйтесь редактировать.
источник
Это утверждение неверно:
Вам не нужно добавлять «ожидание»,
await
это просто удобное ключевое слово в C #, которое позволяет вам писать больше строк кода после вызова, и эти другие строки будут выполняться только после завершения операции сохранения. Но, как вы отметили, этого можно добиться, просто позвонивSaveChanges
вместоSaveChangesAsync
.Но по сути, асинхронный вызов - это гораздо больше. Идея здесь в том, что если есть другая работа, которую вы можете выполнять (на сервере) во время операции сохранения, то вам следует использовать
SaveChangesAsync
. Не используйте "ожидание". Просто позвонитеSaveChangesAsync
, а потом продолжайте параллельно заниматься другими делами. Это включает в себя потенциально в веб-приложении возврат ответа клиенту даже до завершения сохранения. Но, конечно, вы все равно захотите проверить окончательный результат сохранения, чтобы в случае сбоя вы могли сообщить об этом своему пользователю или как-то зарегистрировать его.источник