Ответ 1
Всякий раз, когда вам нужно выполнить действие на удаленном сервере, ваша программа генерирует запрос, отправляет его, а затем ожидает ответа. Я буду использовать 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 "Продолжение выполнения" до 1000 раз. Когда SaveChangesAsync()
заканчивается, выполнение возвращается к функции SaveRecords
, записывая Save Complete
в консоль. Как только все в SaveRecords
завершится, выполнение будет продолжено в MyCallingFunction
прямо там, где это было, когда SaveChangesAsync()
закончил. Смущенный? Вот пример выходных данных:
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!
Или, может быть,
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!
В этом прелесть 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
, вам следует, так как это значительно повысит производительность вашего приложения.