EF6使用.NET 4.5中引入的async和await关键字提供对异步查询和保存的支持。虽然不是所有的程序都能从异步获益,但当处理需要长时间运行、网络或受IO限制的任务时,异步可以用于提升客户端响应和服务器端的可伸缩性。
本篇包括如下主题:
何时真正使用异步
本演练的目的是介绍异步概念,使它易于观察异步和同步程序执行的区别,而不是为了说明异步编程提供好处的任何关键场景。
异步编程的重点在于当前托管线程等待一个操作时,要释放掉它来做其他工作。举个例子,当数据库引擎处理一个查询时,.NET代码什么都没有做。
在客户端程序(WinForms,WPF等)中,异步操作执行时,当前线程可以用来保持UI响应。在服务器端程序(ASP.NET等),线程可用来处理其他的入站请求。这可以减少内存使用或提高服务器的吞吐量。
使用异步的大多数程序不会有明显的好处,甚至还有害处。在提交之前,要使用测试、分析和常识衡量异步在特定场景下的影响。
这是学习异步的一些资源:
- Brandon Bray’s overview of async/await in .NET 4.5
- Asynchronous Programming pages in the MSDN Library
- How to Build ASP.NET Web Applications Using Async (includes a demo of increased server throughput)
创建模型
我们使用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();
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)
});
db.SaveChanges();
// Query for all blogs ordered by name
var blogs = (from b in db.Blogs
orderby b.Name
select b).ToList();
// Write all blogs out to Console
Console.WriteLine();
Console.WriteLine("All blogs:");
foreach (var blog in blogs)
{
Console.WriteLine(" " + blog.Name);
}
}
}
}
}
这段代码调用PerformDatabaseOperations方法保存一个新的Blog到数据库并且从数据库中获取所有的Blogs,最后打印到控制台上。然后,程序写入a quote of the day到控制台。
因为代码是同步的,执行程序可以看到下面的执行流程:
1.SaveChanges开始保存一个Blog到数据库
2.SaveChanges完成
3.查询所有的Blogs命令发送到数据库
4.查询返回,结果写到控制台
5.Quote of the day写到控制台
使其异步
现在程序运行正常,我们可以开始使用新的async和await关键字了。对Programs.cs作如下修改:
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Data.Entity;
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开始保存一个新的Blog到数据库
一旦命令发送到数据库,在当前托管线程上不再计算时间。PerformDatabaseOperations方法返回(即使还没有执行完),Main方法中的程序流程继续。
2.Quote of the day写入到控制台
因为在Main方法中没有其他工作要做,托管线程在Wait调用时阻塞,直到数据库操作完成。一旦完成,PerformDatabaseOperations剩余部分将被执行。
3.SaveChanges完成
4.查询所有Blogs命令发送到数据库
当查询在数据库里查询时,托管线程又一次空闲可以做其他工作。因为其他的执行都已经完成了,线程会在Wain方法调用时停止,直到查询完成。
5.查询返回,结果写入到控制台。
结束语
我们看到使用EF的异步方法是多么的简单。尽管通过简单的控制台程序异步的优势显现不出来,但是相同的策略可以应用到长时间运行或受网络限制的程序,或者引起大量线程增加内存占用的程序。



多表查询如何进行异步查询