EF6异步方法混淆

时间:2014-05-26 06:36:28

标签: c# .net entity-framework asynchronous async-await

最近我们升级到EF6跳跃,以利用我们的webapi控制器中的异步调用,但经过一些在线阅读后,我可以知道

- EF6 async call is not thread safe 

While thread safety would make async more useful it is an orthogonal feature. It is  
unclear that we could ever implement support for it in the most general case, given that 
EF interacts with a graph composed of user code to maintain state and there aren't
easy ways to ensure that this code is also thread safe.

https://entityframework.codeplex.com/wikipage?title=Task-based%20Asynchronous%20Pattern%20support%20in%20EF

但在这个问题中也传达了同样的事情 EF Data Context - Async/Await & Multithreading

但是当我查看MS http://msdn.microsoft.com/en-us/data/jj819165.aspx的样本时 我很困惑,因为如果我看看stackoverflow问题中提供的答案,目前我们似乎没有任何解决方案/模式来实现它与单个数据库上下文的线程安全?

所以我的问题又是如何实现

var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);

是一种使用线程安全功能的正确方法吗?

2 个答案:

答案 0 :(得分:2)

来自您引用的same EF docs

  

目前,EF将检测开发人员是否尝试执行   一次两个异步操作并抛出。

因此,即使在await之后有一个线程切换,这段代码也应该有用,因为它仍然按顺序执行:

var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);

至少,这是预期的工作方式。如果顺序执行仍然产生与线程相关的异常,则应将其报告为EF错误。

另一方面,以下代码很可能会失败,因为我们引入了并行性:

var dbContext = new DbContext();
var somethingTask = dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morethingTask = dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);

await Task.WhenAll(somethingTask, morethingTask);

var something = somethingTask.Result;
var morething = morethingTask.Result;

您需要确保不要将相同的DbContext用于多个待处理的EF操作。

已更新,第一个代码片段实际上可以正常使用EF v6.1.0。

答案 1 :(得分:1)

关于Entity Framework上下文(以及其他ORM)的事情是它们不是线程安全的。这意味着你不应该在多个线程之间共享相同的上下文对象,否则你可能会面临以下一些问题:

  • 未在数据库中提交的实体将在更多内容之间共享 而不是一个执行计划;
  • 上下文抛出的异常,使每个线程无效 使用这种背景,你将如何处理;
  • 因不知道何时处置而导致内存泄漏 上下文(还有人还在使用上下文吗?);

这些只是您通过共享上下文会遇到的一小部分问题。实际上,您应该将上下文视为当前执行计划的工作单元,它将存储仅与您相关的实体和更改。完成所有事情后,应将其处理掉。请看下面的代码:

using(var ctx = new MyDbContext()){
    var car = await ctx.Cars.FindAsync(id);
    car.Owner = new Person{ Name = "John Doe" };
    await ctx.SaveAsync();
}

为什么需要执行异步方法执行?因为当前正在执行的线程,而不是等待数据库查询结果的空闲,将能够执行其他作业,如处理挂起的Web请求,继续执行最终终止的任务等。请注意,每个线程都有开销和线程池不能使用等待操作终止的空闲线程。

您应该尝试做什么,并且因为您正在实现Web应用程序,所以正在使用Web请求的上下文,因为这是您的执行计划。也许这会让你更进一步(Repository and UoW pattern with service layer)。通常我使用带有工厂模式和拦截器的IoC框架,但是如果你想保持简单,你可以为HttpContext.Items创建一个包装器并在请求结束时处理你的上下文。