我们目前处于僵局。
AspNetSynchronizationContext
负责 ASP.NET Web API 执行环境的线程模型,但并不保证经过
await
后异步继续运行在同一线程上。这样做的整个思想是使 ASP.NET 应用程序更具可扩展性,因此较少使用
ThreadPool
中被挂起的同步操作。
然而,DataContext
类(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 */ })
。然而,在 ASP.NET 的背景下,这是
已知的反模式,可能只会损害性能和可扩展性,因为它不会减少满足请求所需的线程数量。
不幸的是,虽然ASP.NET的异步架构非常优秀,但它仍然与一些已经建立的API和模式不兼容(例如,这里有一个类似的案例)。
这真的很遗憾,因为我们在这里没有处理并发API访问,即没有超过一个线程同时尝试访问DataContext
对象。
希望微软在未来的框架版本中会解决这个问题。
[更新] 尽管如此,在大规模上,可能可以将EF逻辑卸载到一个单独的进程中(作为WCF服务运行),该进程将为ASP.NET客户端逻辑提供线程安全的异步API。这样的进程可以使用自定义同步上下文作为事件机器进行编排,类似于Node.js。它甚至可以运行一组类似于Node.js的公寓,每个公寓维护EF对象的线程亲和性。这将允许仍然从异步EF API中受益。
[更新] 这里有一些尝试找到解决此问题的方法。
await
处跳线程。他们确实声明了他们不会使其完全线程安全。 - Stephen ClearyTask.WhenAll
等待两个挂起的SQL请求完成吗?我还没有尝试过使用异步EF,但这对我来说有点令人失望。 - ken2kDbContext
。 - Stephen ClearyDbContext
,那么你只需要确保你的目标是.NET 4.5并使用async
/await
,它就可以正常工作。 - Stephen Cleary