Подтвердить что ты не робот

Entity Framework SaveChanges() и SaveChangesAsync() и Find() vs. FindAsync()

Я искал различия между двумя парами выше, но не нашел статей, объясняющих это явно, а также когда использовать тот или иной.

Так в чем разница между SaveChanges() и SaveChangesAsync()?
И между Find() и FindAsync()?

На стороне сервера, когда мы используем методы Async, нам также нужно добавить await. Таким образом, я не думаю, что он асинхронен на стороне сервера.

Помогает ли это только предотвратить блокировку пользовательского интерфейса в браузере на стороне клиента? Или между ними существуют плюсы и минусы?

4b9b3361

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

Ответ 2

Это утверждение неверно:

На стороне сервера, когда мы используем методы Async, нам также нужно добавить ожидание.

Вам не нужно добавлять "ожидание". "Ожидание" - просто удобное ключевое слово в С#, которое позволяет вам писать больше строк кода после вызова, а остальные строки будут выполняться только после завершения операции сохранения. Но, как вы указали, вы можете сделать это просто, вызвав SaveChanges вместо SaveChangesAsync.

Но принципиально, асинхронный вызов гораздо больше. Идея здесь в том, что если есть другая работа, которую вы можете сделать (на сервере), пока выполняется операция сохранения, вы должны использовать SaveChangesAsync. Не используйте "ожидание". Просто вызовите SaveChangesAsync, а затем продолжайте делать другие вещи параллельно. Это включает потенциально в веб-приложении возврат ответа клиенту еще до завершения сохранения. Но, конечно, вы все равно захотите проверить окончательный результат сохранения, чтобы в случае его отказа вы могли сообщить об этом своему пользователю или зарегистрировать его как-то.

Ответ 3

Мое оставшееся объяснение будет основано на следующем фрагменте кода.

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;
}

enter image description here

Примечания: Поскольку синхронная часть (зеленая) 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;
}

enter image description here

Примечания: Этот случай противоположен первому случаю. Синхронная часть (зеленая) 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;
}

enter image description here

Замечания: Этот случай решит проблему в предыдущих случаях с синхронной частью в асинхронном методе. Задание t немедленно ожидается. В результате продолжение (синее) выполняется в другом потоке, как зеленое. Синхронная часть Main (белого цвета) будет вращаться сразу параллельно JobAsync.

Если вы хотите добавить другие случаи, не стесняйтесь редактировать.