异步查询并保存

注释

EF6 及更高版本 - 实体框架 6 中介绍了本页中讨论的功能、API 等。 如果使用早期版本,某些或全部信息不适用。

EF6 引入了对异步查询和保存的支持,并使用 .NET 4.5 中引入的 async 和 await 关键字 实现。 虽然并非所有应用程序都可能受益于异步,但它可用于在处理长时间运行、网络或 I/O 绑定的任务时提高客户端响应能力和服务器可伸缩性。

何时真正使用异步编程

本演练的目的是以一种便于观察异步程序执行和同步程序执行差异的方式引入异步概念。 本演练不旨在说明异步编程提供优势的任何关键方案。

异步编程主要侧重于释放当前托管线程(运行 .NET 代码的线程)以执行其他工作,同时等待不需要托管线程的任何计算时间的操作。 例如,虽然数据库引擎正在处理查询,但 .NET 代码不会执行任何操作。

在客户端应用程序(WinForms、WPF 等)中,当前线程可用于在执行异步操作时保持 UI 响应。 在服务器应用程序中(ASP.NET 等)中,线程可用于处理其他传入请求 - 这可以减少内存使用率和/或增加服务器的吞吐量。

在大多数使用异步的应用程序中,没有任何明显的好处,甚至可能有害。 在决定使用异步之前,请使用测试、性能分析和常识来衡量异步在特定场景中的影响。

下面是一些其他资源,用于了解异步:

创建模型

我们将使用 Code First 工作流 创建模型并生成数据库,但异步功能将处理所有 EF 模型,包括使用 EF 设计器创建的模型。

  • 创建控制台应用程序并将其调用 AsyncDemo
  • 添加 EntityFramework NuGet 包
    • 在解决方案资源管理器中,右键单击 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 将新的 Blog 保存到数据库的方法,然后检索数据库中的所有 博客 并将其打印到 控制台。 之后,程序会将当天的报价写入 控制台

由于代码是同步的,因此在运行程序时,我们可以观察以下执行流:

  1. SaveChanges 开始将新的 博客 推送到数据库
  2. SaveChanges 完成
  3. 所有博客的查询将被发送到数据库。
  4. 查询返回和结果写入 控制台
  5. 今日名言将写入 控制台

同步输出  

 

使其异步

现在,我们已经启动并运行了程序,接下来可以开始使用新的异步和 await 关键字。 我们已对Program.cs进行了以下更改

  1. 第 2 行:using 语句用于 System.Data.Entity 命名空间,允许我们访问 EF 的异步扩展方法。
  2. 第 4 行:System.Threading.Tasks 命名空间的 using 语句允许我们使用 Task 类型。
  3. 第 12 行和第 18 行:我们正在捕获一个任务来监视PerformSomeDatabaseOperations(第 12 行)的进度,并在程序的所有工作完成后(第 18 行)阻塞程序执行以等待该任务完成。
  4. 第 25 行:我们已将 PerformSomeDatabaseOperations 更新为 async 并返回一个 Task
  5. 第 35 行:我们现在正在调用 SaveChanges 的异步版本,并等待其完成。
  6. 第 42 行:我们现在调用 Async 版本 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. 所有博客的查询将被发送到数据库。
    同样,在数据库中处理查询时,托管线程可以自由执行其他工作。 由于所有其他执行都已完成,线程只会在等待调用时停止。
  5. 查询返回和结果写入 控制台

异步输出  

 

外卖

我们现在了解了使用 EF 的异步方法是多么容易。 尽管异步的优点在简单的控制台应用中可能不太明显,但在长时间运行或网络绑定活动可能会阻止应用程序或导致大量线程增加内存占用的情况下,可以应用这些相同的策略。