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

C# EF数据上下文-异步/等待&;多线程,c#,.net,multithreading,entity-framework,async-await,C#,.net,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 dbContex

我经常使用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);
事实上,我有时会看到一些例外情况,例如:

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


对于每个对数据库的异步调用,使用单独的
using(new DbContext…
块是否正确?仅仅执行同步是否可能更有益

我们现在处于僵局
AspNetSynchronizationContext
,负责ASP.NET Web API执行环境的线程模型,并不保证在
Wait
之后的异步继续将在同一线程上进行。这样做的全部目的是使ASP.NET应用程序更具可伸缩性,因此
ThreadPool
中较少的线程被挂起的同步操作阻塞

但是, 不是线程安全的,因此在
DataContext
API调用之间可能发生线程切换的情况下不应使用它。在每个异步调用中使用构造的单独的
将不会有帮助,或者:

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中,它们的
使用
范围应限于两个相邻的
等待
调用之间的范围

使用
wait Task.Run(()=>{/*dodatacontext stuff here*/})将一堆同步的
DataContext
调用卸载到一个单独的线程可能很诱人。但是,这可能是错误的,特别是在ASP.NET环境中,它可能只会影响性能和可伸缩性,因为它不会减少满足请求所需的线程数

不幸的是,尽管ASP.NET的异步体系结构很棒,但它仍然与一些已建立的API和模式不兼容(例如,这里是)。 这尤其令人伤心,因为我们这里没有处理并发API访问,也就是说,没有多个线程同时尝试访问
DataContext
对象

希望微软能在未来版本的框架中解决这个问题

[UPDATE]但在大规模情况下,可能会将EF逻辑卸载到单独的进程(作为WCF服务运行),该进程将为ASP.NET客户端逻辑提供线程安全的异步API。类似于Node.js,这样的过程可以使用自定义同步上下文作为事件机进行编排。它甚至可以运行一个类似Node.js的单元池,每个单元都维护EF对象的线程关联。这将允许您仍然受益于async EF API

[UPDATE]这里是为了找到这个问题的解决方案。

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

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


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

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

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);
}
公共异步任务GetProjectAsync(字符串名称) { DBContext _localdb=new DBContext(); return wait _localdb.Projects.FirstOrDefaultAsync(x=>x.Name==Name); }

这真令人失望。web应用程序可以从异步性中获得真正收益的一个因素是数据库调用。。。但不支持w/EF。“在与最初创建对象的线程不同的线程上,这不是DataContext所期望的。”请解释一下,DbContext是否记得创建它的线程?到底是什么问题?@alpha mouse,AFAIR不深入研究,它取决于一些助手类,这些助手类反过来使用线程本地存储(TLS)。这可能在最近的EF版本中发生了变化。@Simon_Weaver,
TransactionScope AsyncFlowOption
将解决
TransactionScope
的类似问题,但它与
DataContext
无关。注:OP编辑了问题,并将
DataContext
替换为
DbContext
,以及