线程安全的Entity Framework 6

20

我刚开始测试EF6及其异步函数,但惊讶地发现它们不是线程安全的,我原以为这正是它们的目的。

我已经拥有自己的Task扩展方法多年了,但我期待EF能使它们变得线程安全。

至少我的基于Task的函数使用了lock来避免相互干扰。但EF6甚至没有这么做。但主要问题与我的代码共享。例如,尝试发出异步查询,然后在其完成之前尝试访问导航属性(在同一上下文中的预加载完全分离的实体上触发惰性加载)。这可能由UI触发,也可能由其他代码在当前函数之外触发,或者由数十种其他情况触发。

据我所知,在dbContext中仅有两个可变资源(在实体之间共享)是连接和更改跟踪(缓存)。如果我们可以在这些功能周围添加锁定,则我们将拥有线程安全的上下文。

我们甚至可以分两个阶段进行。如果我们可以实现一个提供程序来锁定用于查询数据库的一个集中的函数。那么任何非跟踪查询-通过返回非实体(匿名)对象或调用AsNoTracking()-都将是线程安全的,并且即使另一个线程可能正在请求一个惰性加载的对象,也可以在异步函数中调用它们。

我们的可扩展性不会比现在使用每个线程一个上下文更差,即使您尝试跳过一个await以引入一些并行性或在事件系统(如wpf)中工作,该系统可能会在等待函数返回任务后触发。

所以我的问题是,有人实现了这样的提供程序吗?或者有谁愿意与我合作实现它?


4
有趣的是,EF6不仅不安全,而且是多线程的。EF6和“Async”函数的重点并不是线程处理。实际上,“async”和“await”不是线程框架,而是异步框架。你可以编写单线程异步应用程序,事实上,在node.js中只能这样做。 - Aron
2
我认为在使用EF6的async时,一般的指导是完全禁用延迟加载。否则,您将拥有异步和阻塞代码的混合,这将始终具有行为怪异性。 - Stephen Cleary
1
@StephenCleary 这可能是由于当前实现的一般性建议。但是,如果有人要开发一个在数据库访问周围进行lock的提供程序,那么我们将能够同时使用两者。 - Rabbi
2
@Rabbi:关键是你不会想要这样做。懒加载会无端地阻塞异步操作的线程。如果你要么Include相关实体,要么执行单独的(异步)后续请求,你的代码就完全是异步的了。 - Stephen Cleary
4
大多数情况下,异步的“任务”是基于(逻辑上的)事件,而不是线程。特别是在EF6中,这一点是正确的。 - Stephen Cleary
显示剩余7条评论
1个回答

9
我认为你面临的是一个架构问题。你所描述的是一个应用程序,其中UI直接使用EF对象,这违反了“关注点分离”范例。
在我的项目中,我在模型层上使用自定义的线程安全缓存,让所有事情都发生在模型层上。我使用众所周知的AsyncLock在我的缓存中实现了线程安全性。
DbContext对象和每个EF CRUD相关操作的生命周期非常有限。每个CRUD操作都会实例化自己的DbContext,并将模型对象返回到缓存中,然后回收上下文。我的应用程序使用缓存作为抽象层,缓存使用EF作为数据库抽象层。
例如,通过在模型层上实现自定义方法来探索对象上的附加属性,该方法以对象ID作为参数,并将相关对象列表返回给缓存。UI询问缓存,然后缓存询问EF,一旦可用,调用缓存会向UI返回对象。就这么简单。
EntityFramework没有设计成线程安全的,因此无法以多线程方式使用它。(EF thread safety)

与直接访问DbContext不同,您需要构建一个可以以多线程方式访问的模型层。您的模型可以对数据库进行多个并行调用,但请记住每个调用必须实例化并保留它自己的DbContext。在每个调用结束时,相关的DbContext必须被处理。

DbContext实例化非常快,唯一的缺点是网络延迟。这就是为什么内存缓存是一个好主意。


1
如果我能为每个可以通过更频繁地实例化DbContext来解决的EF问题获得五分镍币... - Casey

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接