EF数据上下文 – asynchronous/等待和multithreading

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

System.Data.Entity命名空间在这里提供了各种帮助器扩展,如FirstOrDefaultAsyncContainsAsyncCountAsync等等。

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

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的一个错误。