Асинхронный запрос и сохранение

Замечание

EF6 и более поздние версии — функции, API и т. д., рассмотренные на этой странице, были представлены в Entity Framework 6. Если вы используете более раннюю версию, некоторые или все сведения не применяются.

EF6 представила поддержку асинхронных запросов и сохранения с использованием ключевых слов async и await, которые были введены в .NET 4.5. Хотя не все приложения могут выиграть от асинхронного подхода, его можно использовать для повышения отзывчивости клиентов и масштабируемости сервера при обработке длительных, сетевых или операций ввода-вывода задач.

Когда действительно использовать async

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

Асинхронное программирование в основном сосредоточено на освобождении текущего управляемого потока (поток под управлением кода .NET) для выполнения другой работы во время ожидания операции, которая не требует времени вычислений из управляемого потока. Например, в то время как ядро СУБД обрабатывает запрос, не требуется ничего делать с помощью кода .NET.

В клиентских приложениях (WinForms, WPF и т. д.) текущий поток можно использовать для реагирования пользовательского интерфейса при выполнении асинхронной операции. В серверных приложениях (ASP.NET и т. д.) поток можно использовать для обработки других входящих запросов. Это может снизить использование памяти и /или увеличить пропускную способность сервера.

В большинстве приложений, использующих асинхронный код, не будет заметных преимуществ и даже может быть вредным. Используйте тесты, профилирование и здравый смысл, чтобы оценить влияние асинхронности в вашей конкретной ситуации, прежде чем принимать решение о её использовании.

Ниже приведены дополнительные ресурсы для получения сведений об асинхронном коде:

Создание модели

Мы будем использовать рабочий процесс Code First для создания модели и создания базы данных, однако асинхронная функциональность будет работать со всеми моделями EF, включая созданные с помощью конструктора EF.

  • Создание консольного приложения и вызов asyncDemo
  • Добавление пакета NuGet EntityFramework
    • В обозревателе решений щелкните правой кнопкой мыши проект AsyncDemo
    • Выберите "Управление пакетами NuGet...
    • В диалоговом окне "Управление пакетами NuGet" выберите вкладку "Интернет" и выберите пакет EntityFramework
    • Щелкните Установить.
  • Добавление класса Model.cs со следующей реализацией
    using System.Collections.Generic;
    using System.Data.Entity;

    namespace AsyncDemo
    {
        public class BloggingContext : DbContext
        {
            public DbSet<Blog> Blogs { get; set; }
            public DbSet<Post> Posts { get; set; }
        }

        public class Blog
        {
            public int BlogId { get; set; }
            public string Name { get; set; }

            public virtual List<Post> Posts { get; set; }
        }

        public class Post
        {
            public int PostId { get; set; }
            public string Title { get; set; }
            public string Content { get; set; }

            public int BlogId { get; set; }
            public virtual Blog Blog { get; set; }
        }
    }

 

Создание синхронной программы

Теперь, когда у нас есть модель EF, давайте напишите код, который использует его для выполнения доступа к данным.

  • Замените содержимое Program.cs следующим кодом.
    using System;
    using System.Linq;

    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                PerformDatabaseOperations();

                Console.WriteLine("Quote of the day");
                Console.WriteLine(" Don't worry about the world coming to an end today... ");
                Console.WriteLine(" It's already tomorrow in Australia.");

                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            public static void PerformDatabaseOperations()
            {
                using (var db = new BloggingContext())
                {
                    // Create a new blog and save it
                    db.Blogs.Add(new Blog
                    {
                        Name = "Test Blog #" + (db.Blogs.Count() + 1)
                    });
                    Console.WriteLine("Calling SaveChanges.");
                    db.SaveChanges();
                    Console.WriteLine("SaveChanges completed.");

                    // Query for all blogs ordered by name
                    Console.WriteLine("Executing query.");
                    var blogs = (from b in db.Blogs
                                orderby b.Name
                                select b).ToList();

                    // Write all blogs out to Console
                    Console.WriteLine("Query completed with following results:");
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(" " + blog.Name);
                    }
                }
            }
        }
    }

Этот код вызывает PerformDatabaseOperations метод, который сохраняет новый блог в базе данных, а затем извлекает все блоги из базы данных и выводит их в консоль. После этого программа записывает цитату дня в консоль.

Так как код синхронен, при запуске программы можно наблюдать следующий поток выполнения:

  1. SaveChanges начинает отправлять новый блог в базу данных
  2. SaveChanges Завершает
  3. Запрос ко всем блогам отправляется в базу данных
  4. Возвращаемые запросы и результаты записываются в консоль
  5. Цитата дня записывается в консоль

Синхронизировать выходные данные  

 

Сделать его асинхронным

Теперь, когда наша программа запущена и работает, мы можем начать использовать новые ключевые слова async и await. Мы внесли следующие изменения в Program.cs

  1. Строка 2. Оператор using для пространства имен System.Data.Entity предоставляет нам доступ к асинхронным методам расширения EF.
  2. Строка 4. Оператор using для пространства имен System.Threading.Tasks позволяет использовать тип Task.
  3. Строка 12 и 18. Мы создаем задачу, которая отслеживает ход выполнения PerformSomeDatabaseOperations (строка 12), а затем блокируем выполнение программы до завершения этой задачи после того, как вся работа программы выполнена (строка 18).
  4. Строка 25: Мы обновили PerformSomeDatabaseOperations, чтобы было помечено как async и возвращало Task.
  5. Строка 35: Мы теперь вызываем асинхронную версию SaveChanges и ожидаем её завершения.
  6. На строке 42 мы теперь вызываем асинхронную версию метода ToList и ожидаем результата.

Полный список доступных методов расширения в System.Data.Entity пространстве имен см. в QueryableExtensions классе. Вам также потребуется добавить using System.Data.Entity в директивы using.

    using System;
    using System.Data.Entity;
    using System.Linq;
    using System.Threading.Tasks;

    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                var task = PerformDatabaseOperations();

                Console.WriteLine("Quote of the day");
                Console.WriteLine(" Don't worry about the world coming to an end today... ");
                Console.WriteLine(" It's already tomorrow in Australia.");

                task.Wait();

                Console.WriteLine();
                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }

            public static async Task PerformDatabaseOperations()
            {
                using (var db = new BloggingContext())
                {
                    // Create a new blog and save it
                    db.Blogs.Add(new Blog
                    {
                        Name = "Test Blog #" + (db.Blogs.Count() + 1)
                    });
                    Console.WriteLine("Calling SaveChanges.");
                    await db.SaveChangesAsync();
                    Console.WriteLine("SaveChanges completed.");

                    // Query for all blogs ordered by name
                    Console.WriteLine("Executing query.");
                    var blogs = await (from b in db.Blogs
                                orderby b.Name
                                select b).ToListAsync();

                    // Write all blogs out to Console
                    Console.WriteLine("Query completed with following results:");
                    foreach (var blog in blogs)
                    {
                        Console.WriteLine(" - " + blog.Name);
                    }
                }
            }
        }
    }

Теперь, когда код является асинхронным, мы можем наблюдать другой поток выполнения при запуске программы:

  1. SaveChanges начинает отправлять новый блог в базу данных
    После отправки команды в базу данных не требуется больше времени вычислений в текущем управляемом потоке. Метод PerformDatabaseOperations возвращает (даже если он не завершил выполнение) и поток программы в методе Main продолжается.
  2. Цитата дня записывается в консоль
    Так как в методе Main больше нет никаких действий, управляемый поток блокируется при вызове Wait до завершения операции базы данных. Завершив его, оставшаяся часть будет PerformDatabaseOperations выполнена.
  3. SaveChanges Завершает
  4. Запрос ко всем блогам отправляется в базу данных
    Опять же, управляемый поток может выполнять другие действия во время обработки запроса в базе данных. Поскольку все остальные выполнения завершены, поток, тем не менее, просто остановится на вызове "Wait".
  5. Возвращаемые запросы и результаты записываются в консоль

Асинхронные выходные данные  

 

Основной вывод

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