EF数据上下文 – asynchronous/等待和multithreading
我经常使用async / await来确保ASP.NET MVC Web API线程不被更长时间运行的I / O和networking操作阻塞,特别是数据库调用。
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);
事实上,我有时会看到例外,例如:
System.InvalidOperationException:连接未closures。 连接的当前状态是打开的。
那么是否正确的模式使用单独的using(new DbContext...)
块为每个asynchronous调用数据库? 那么执行同步操作可能会更有益吗?
我们在这里有一个僵局。 AspNetSynchronizationContext
负责ASP.NET Web API执行环境的线程模型,并不保证await
之后的asynchronous延续将在同一个线程上发生。 这样做的全部想法是使ASP.NET应用程序更具可伸缩性,从而减less了ThreadPool
中的线程数量,并使用挂起的同步操作进行阻塞。
但是, DataContext
不是线程安全的,因此不应在跨越DataContext
API调用时可能发生线程切换的情况下使用它。 每个asynchronous调用一个单独的using
构造不会帮助,或者:
var something; using (var dataContext = new DataContext()) { something = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1); }
这是因为DataContext.Dispose
可能会在与最初创build的对象不同的线程上执行,而这不是DataContext
所期望的。
如果你想坚持使用DataContext
API, 同步调用它似乎是唯一可行的select。 我不知道这个声明是否应该扩展到整个EF API,但我想用DataContext
API创build的任何子对象也可能不是线程安全的。 因此,在ASP.NET中,它们的using
范围应该被限制在两个相邻的await
呼叫之间。
这可能是诱人卸载一堆同步DataContext
调用到一个单独的线程与await Task.Run(() => { /* do DataContext stuff here */ })
。 但是,这将是一个已知的反模式 ,特别是在ASP.NET可能会损害性能和可伸缩性的情况下,因为它不会减less完成请求所需的线程数。
不幸的是,尽pipeASP.NET的asynchronous架构非常棒,但仍然与一些已经build立的API和模式不兼容(例如,这里也是类似的情况 )。 这特别令人伤心,因为我们没有在这里处理并发的API访问,即只有一个线程试图同时访问一个DataContext
对象。
希望微软将在未来版本的框架中解决这个问题。
[更新]尽pipe如此,也许可以将EF逻辑卸载到单独的进程(作为WCF服务运行),这将向ASP.NET客户端逻辑提供线程安全的asynchronousAPI。 可以使用自定义同步上下文作为事件机器来编排此类stream程,类似于Node.js. 它甚至可能会运行一个类似Node.js的公寓池,每个公寓都保持EF对象的线程关系。 这将允许仍然受益于asynchronousEF API。
[更新]这里有一些尝试find解决这个问题的方法。
DataContext
类是LINQ to SQL的一部分。 它不理解async
/ await
AFAIK,不应该与entity frameworkasync
扩展方法一起使用。
只要您使用EF6或更高版本, DbContext
类就可以正常工作; 但是,每次运行DbContext
实例只能执行一次操作(同步或asynchronous)。 如果你的代码实际上使用了DbContext
,那么检查你的exception的调用堆栈,并检查是否有任何并发使用(例如, Task.WhenAll
)。
如果您确定所有的访问都是连续的,那么请发布一个最简单的repro和/或将其报告为Microsoft Connect的一个错误。