EF数据上下文 - 异步/等待&多线程

时间:2014-01-06 09:22:47

标签: c# .net multithreading entity-framework async-await

我经常使用 async / await 来确保ASP.NET MVC Web API线程不被长时间运行的I / O和网络操作阻止,特别是数据库调用。

System.Data.Entity 命名空间在此处提供了各种帮助程序扩展,例如 FirstOrDefaultAsync ContainsAsync CountAsync < / em>等等。

但是,由于数据上下文不是线程安全的,这意味着以下代码存在问题:

var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);

事实上,我有时会看到例外情况:

  

System.InvalidOperationException:   连接没有关闭。连接的当前状态是打开的。

那么正确的模式是否使用单独的using(new DbContext...)块来进行对数据库的每次异步调用?相反,执行同步可能更有益吗?

3 个答案:

答案 0 :(得分:57)

DataContext class是LINQ to SQL的一部分。它无法理解async / await AFAIK,也不应与实体框架async扩展方法一起使用。

只要您使用EF6或更高版本,DbContext class就能正常使用async;但是,每次运行DbContext个实例只能有一个操作(同步或异步)。如果您的代码实际上使用的是DbContext,那么请检查异常的调用堆栈并检查是否存在任何并发使用情况(例如Task.WhenAll)。

如果您确定所有访问都是连续的,那么请发布一个最小的repro和/或将其作为错误报告给Microsoft Connect。

答案 1 :(得分:31)

我们这里陷入僵局。 AspNetSynchronizationContext负责ASP.NET Web API执行环境的线程模型,并不保证await之后的异步延续将在同一个线程上发生。这样做的整个想法是使ASP.NET应用程序更具可伸缩性,因此来自ThreadPool的更少线程被挂起的同步操作阻止。

然而,DataContext class (part of LINQ to SQL ) 不是线程安全的,因此不应该在DataContext API调用可能发生线程切换的地方使用它。每个异步调用单独的using构造将帮助,或者:

var something;
using (var dataContext = new DataContext())
{
    something = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
}

这是因为DataContext.Dispose可能在与最初创建对象的线程不同的线程上执行,而这不是DataContext所期望的。

如果您想坚持使用DataContext API,将其称为同步似乎是唯一可行的选择。我不确定该语句是否应扩展到整个EF API,但我认为使用DataContext API创建的任何子对象也可能不是线程安全的。因此,在ASP.NET中,using范围应限制在两个相邻await调用之间。

将一堆同步DataContext调用卸载到await Task.Run(() => { /* do DataContext stuff here */ })的单独线程可能很诱人。但是,这将是a known anti-pattern,特别是在ASP.NET的上下文中,它可能只会损害性能和可伸缩性,因为它不会减少完成请求所需的线程数。

不幸的是,虽然ASP.NET的异步架构很棒,但它仍然与某些已建立的API和模式不兼容(例如,这里是a similar case)。 这特别令人难过,因为我们在这里没有处理并发API访问,即只有一个线程试图同时访问DataContext对象。

希望Microsoft能够在未来的Framework版本中解决这个问题。

[更新] 但是,在大规模上,可能可以将EF逻辑卸载到单独的进程(作为WCF服务运行),这将为线程提供线程安全的异步API。 ASP.NET客户端逻辑。这样的过程可以使用自定义同步上下文作为事件机器进行编排,类似于Node.js.它甚至可以运行类似Node.js的公寓池,每个公寓都保持EF对象的线程亲和力。这样就可以从异步EF API中受益。

[更新] 以下some attempt可以找到解决此问题的方法。

答案 2 :(得分:-2)

异步编程是并行编程的一种方式,其中一个工作单元与主应用程序线程分开运行,并向调用线程通知其完成,失败或进度。使用异步编程可以获得的主要好处是提高了应用程序性能和响应能力。

Entity Framework 6.0支持异步编程,这意味着您可以异步查询数据和保存数据。通过使用async / await,您可以轻松地为实体框架编写异步编程。

示例:

public async Task<Project> GetProjectAsync(string name) 
{
    DBContext _localdb = new DBContext();
    return await _localdb.Projects.FirstOrDefaultAsync(x => x.Name == name);
}

https://rajeevdotnet.blogspot.com/2018/06/entity-framework-asynchronous.html