Каждый раз, когда вам нужно выполнить действие на удаленном сервере, ваша программа генерирует запрос, отправляет его, а затем ожидает ответа. Я буду использовать 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, вы должны, поскольку это значительно повысит производительность вашего приложения.