Anshul Nigam
Anshul Nigam

Reputation: 1628

EF6 Async Methods Confusion

Recently we upgraded to EF6 hopping to take advantage of its async call in our webapi controller but after some online reading i can to know

- 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.

but same thing is conveyed in this question EF Data Context - Async/Await & Multithreading

but when i looked at samples from MS http://msdn.microsoft.com/en-us/data/jj819165.aspx i am confused , because if i look at answers provided in stackoverflow question it seems currently we dont have any solution/pattern to implement it thread safe with single db context?

So my question is again how to achieve

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

is a correct way with thread safe feature?

Upvotes: 2

Views: 2152

Answers (2)

noseratio
noseratio

Reputation: 61666

From the same EF docs you quoted:

For the moment, EF will detect if the developer attempts to execute two async operations at one time and throw.

So, this code should work even if there's a thread switch after await, because it's still executed sequentially:

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

At least, this is how it is expected to work. If sequential execution still produces a threading-related exception, this should be reported as an EF bug.

On the other hand, the following code will most likely fail, because we introduce parallelism:

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;

You need to make sure you don't use the same DbContext with more than one pending EF operation.

Updated, the 1st code fragment actually works fine with EF v6.1.0, as expected.

Upvotes: 2

João Simões
João Simões

Reputation: 1361

The thing about Entity Framework contexts (and other ORMs for that matter) is that they are not thread safe. What this means it that you shouldn't share the same context object between more than one thread or you could face some of the following problems:

  • Entities not committed in the database would be shared between more than one execution plan;
  • Exceptions thrown by the context, invalidating every thread that is using that context, and how would you take care of that;
  • Memory leaks that could happen by not knowing when to dispose the context (is anyone still using the context?);

These are just a few subset of problems you would face by sharing the context. In fact, you should see the context as a unit of work for your current execution plan, that will store the entities and changes only relevant to you. After you complete everything, it should be disposed. Take a look at the following snippet:

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

Why do you need async method executions then? Because the thread currently executing, instead of waiting idle for the result of your database query, will be able to execute other jobs, like taking care of pending web requests, continue the execution of tasks that eventually terminated, etc. Note that every thread has an overhead and your thread pool can't use threads that are idle waiting for an operation to terminate.

What you should try to do, and since you are implementing a Web Application, is using a context by web request, because that's your execution plan. Maybe this can lead you further (Repository and UoW pattern with service layer). Normally I use IoC frameworks with factory patterns and interceptors, but if you want to keep it simple, you may create a wrapper for the HttpContext.Items and disposing your context at the end of the request.

Upvotes: 1

Related Questions