我经常使用 异步/等待 以确保 ASP.NET MVC Web API 线程不会被长时间运行的 I/O 和网络操作(特别是数据库调用)阻塞。
System.Data.Entity namespace 在这里提供了多种辅助扩展,例如 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);
事实上,我有时会看到异常(exception)情况,例如:
System.InvalidOperationException:
The connection was not closed. The connection's current state is open.
那么使用单独的 using(new DbContext...) 是正确的模式吗?为每个对数据库的异步调用阻塞?那么只执行同步是否可能更有益?
Best Answer-推荐答案 strong>
我们在这里陷入了僵局。 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 同时对象。
希望微软能在框架的 future 版本中解决这个问题。
[更新] 但是,在大规模上,可以将 EF 逻辑卸载到一个单独的进程(作为 WCF 服务运行),该进程将为 ASP.NET 客户端逻辑提供线程安全的异步 API。此类过程可以使用自定义同步上下文作为事件机进行编排,类似于 Node.js。它甚至可以运行一个类似 Node.js 的单元池,每个单元维护 EF 对象的线程关联。这将允许仍然受益于异步 EF API。
[更新] 这是some attempt找到解决此问题的方法。
关于c# - EF 数据上下文 - 异步/等待和多线程,我们在Stack Overflow上找到一个类似的问题:
https://stackoverflow.com/questions/20946677/
|