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

Ale*_*lex 47 .net c# multithreading entity-framework async-await

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

System.Data.Entity的命名空间提供各种帮助这里的扩展,如FirstOrDefaultAsync,ContainsAsync,CountAsync等等.

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

var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);
Run Code Online (Sandbox Code Playgroud)

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

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

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

Ste*_*ary 60

DataContext是SQL LINQ的一部分.它不理解async/ awaitAFAIK,不应与Entity Framework async扩展方法一起使用.

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

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

  • 我相信你可以同时做多个请求,只要它们都有自己的`DbContext`. (7认同)
  • 我想知道,为什么这不是一个公认的答案? (7认同)
  • @Noseratio所以在使用异步API与EF独特的一点是要有长时间运行的请求更多的可扩展性(因为你不需要阻塞线程的线程池等待请求完成).这只是async/await的两个重要优势之一,即可扩展性和性能.如果你不能通过同时查询多个请求来提高性能(SQL Server会完美地处理这个问题),那就非常令人失望了.感谢关于这个主题的所有问题/答案,这非常有趣. (3认同)

nos*_*tio 32

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

但是, DataContext该类(LINQ to SQL的一部分) 不是线程安全的,因此不应该在DataContextAPI调用可能发生线程切换的地方使用它.一个单独的using每个异步调用构造将不会帮助,无论是:

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

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

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

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

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

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

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

[更新]以下是尝试找到此问题的解决方案.

  • 这真的令人失望.web应用程序可以从异步性中获得真正收益的​​ one*元素是数据库调用......但是不支持w/EF. (8认同)
  • @Simon_Weaver,`TransactionScopeAsyncFlowOption`将解决`TransactionScope`的类似问题,但AFAIK它与`DataContext`无关.注意OP已经编辑了问题并用'DbContext`替换了`DataContext`,而'DbContext`已经是`async`友好的(但不是cuncurrency-frienldy,请参阅Stephen的回答). (2认同)