使用 Entity Framework 异步方法和 SQL Server Compact 的阻塞行为
Blocking behaviour with Entity Framework Async methods and SQL Server Compact
我有一个 MVVM 应用程序调用数据服务来获取一些要绑定的数据。数据服务通过 Entity Framework 6.
访问 SQL Server Compact (v4.0) 数据库
数据(当前)需要几秒钟才能加载,当同步调用时,这(不足为奇)会阻塞 GUI 线程。其中大部分我假设是 IO 绑定的,所以我添加了一个等效的 Async 方法以异步方式执行数据加载,以允许 GUI 保持响应。但是,当我使用 EF Async 方法时,它似乎没有任何区别,GUI 线程仍然阻塞,时间大致相同。
我知道使用 Async 调用不会将工作转移到不同的线程,但是我假设这个 activity 的大部分是 IO 绑定的。因此,使用 EF Async 方法应该允许调用 (GUI) 线程继续执行其他工作,但这没有任何区别。
例如,如果我编写 LoadData 方法以使用 EF 同步加载方法,则 GUI 会阻塞,直到数据加载完毕:
public ObservableCollection<Data> GetData()
{
//Do IO bound activity...
Context.DataTable1.Include(...).Load();
//for demo purposes, just return some data
return Context.DataTable1.Local; //(ObservableCollection<Data>)
}
加载数据的异步方法如下所示:
public async Task<ObservableCollection<Data>> GetDataAsync()
{
//Do IO bound activity...
var task = Context.DataTable1.Include(...).LoadAsync();
await task;
//for demo purposes, just return some data
return Context.DataTable1.Local; //(ObservableCollection<Data>)
}
令人惊讶的是(对我来说)我得到了相同的结果并且它阻塞调用线程的时间长度大致相同(我在上面放了一个秒表)。
我开始考虑除了数据库 IO 绑定工作之外,可能还有一些最小量的 CPU 绑定 activity 导致了阻塞。所以我最终尝试使用 Task.Run():
在后台线程上执行工作
public async Task<ObservableCollection<Data>> GetDataAsync()
{
//Do IO bound activity...
Task<ObservableCollection<Competition>> task = Task.Run(async () =>
{
//Do IO bound activity...
var task = Context.DataTable1.Include(...).LoadAsync();
await task;
//for demo purposes, just return some data
return Context.DataTable1.Local; //(ObservableCollection<Data>)
});
var result = await task;
return result;
}
有了这个,GUI 显然不会阻塞,并且会一直响应。
我已经阅读了很多关于此的文章,包括帖子 here and and blog posts by Stephen Cleary about when not to (here) and when to (here) 使用 Task.Run。我知道我上面的最后一个例子仍然是阻塞的,它只是阻塞了一个线程池线程。我真正不明白的是,为什么在访问 EF 异步方法时它似乎没有提供任何好处?
会不会是在有 IO activity 进行时,也有足够的 CPU 绑定工作导致阻塞?响应时间肯定比 50 毫秒长得多,接近 运行 所有查询的时间接近 2 或 3 秒,因此这种类型的 activity 可以开始证明使用 Task.Run?
或者,我是否错误地编写了 Async 方法,因此它仍然处于阻塞状态?
我还想知道 EF 的 SQL Compact 提供程序的异步方法是否出于某种原因回落到标准同步调用?
SQL Server Compact ADO.NET 提供程序不提供任何异步 API。但是加载数据永远不会花费几秒,除非您正在获取 1000 行。启动应用程序时初始化 dbContext.connection 对象,并在应用程序的整个生命周期内保留为空且未使用。第一次也有 dbContext 的初始化成本。
我有一个 MVVM 应用程序调用数据服务来获取一些要绑定的数据。数据服务通过 Entity Framework 6.
访问 SQL Server Compact (v4.0) 数据库数据(当前)需要几秒钟才能加载,当同步调用时,这(不足为奇)会阻塞 GUI 线程。其中大部分我假设是 IO 绑定的,所以我添加了一个等效的 Async 方法以异步方式执行数据加载,以允许 GUI 保持响应。但是,当我使用 EF Async 方法时,它似乎没有任何区别,GUI 线程仍然阻塞,时间大致相同。
我知道使用 Async 调用不会将工作转移到不同的线程,但是我假设这个 activity 的大部分是 IO 绑定的。因此,使用 EF Async 方法应该允许调用 (GUI) 线程继续执行其他工作,但这没有任何区别。
例如,如果我编写 LoadData 方法以使用 EF 同步加载方法,则 GUI 会阻塞,直到数据加载完毕:
public ObservableCollection<Data> GetData()
{
//Do IO bound activity...
Context.DataTable1.Include(...).Load();
//for demo purposes, just return some data
return Context.DataTable1.Local; //(ObservableCollection<Data>)
}
加载数据的异步方法如下所示:
public async Task<ObservableCollection<Data>> GetDataAsync()
{
//Do IO bound activity...
var task = Context.DataTable1.Include(...).LoadAsync();
await task;
//for demo purposes, just return some data
return Context.DataTable1.Local; //(ObservableCollection<Data>)
}
令人惊讶的是(对我来说)我得到了相同的结果并且它阻塞调用线程的时间长度大致相同(我在上面放了一个秒表)。
我开始考虑除了数据库 IO 绑定工作之外,可能还有一些最小量的 CPU 绑定 activity 导致了阻塞。所以我最终尝试使用 Task.Run():
在后台线程上执行工作public async Task<ObservableCollection<Data>> GetDataAsync()
{
//Do IO bound activity...
Task<ObservableCollection<Competition>> task = Task.Run(async () =>
{
//Do IO bound activity...
var task = Context.DataTable1.Include(...).LoadAsync();
await task;
//for demo purposes, just return some data
return Context.DataTable1.Local; //(ObservableCollection<Data>)
});
var result = await task;
return result;
}
有了这个,GUI 显然不会阻塞,并且会一直响应。
我已经阅读了很多关于此的文章,包括帖子 here and
会不会是在有 IO activity 进行时,也有足够的 CPU 绑定工作导致阻塞?响应时间肯定比 50 毫秒长得多,接近 运行 所有查询的时间接近 2 或 3 秒,因此这种类型的 activity 可以开始证明使用 Task.Run?
或者,我是否错误地编写了 Async 方法,因此它仍然处于阻塞状态?
我还想知道 EF 的 SQL Compact 提供程序的异步方法是否出于某种原因回落到标准同步调用?
SQL Server Compact ADO.NET 提供程序不提供任何异步 API。但是加载数据永远不会花费几秒,除非您正在获取 1000 行。启动应用程序时初始化 dbContext.connection 对象,并在应用程序的整个生命周期内保留为空且未使用。第一次也有 dbContext 的初始化成本。