10

Within an ASP.NET MVC Application I'm recieving the following error message for one of my controller methods that uses my Entity Framework context.

A second operation started on this context before a previous asynchronous operation completed. Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. Any instance members are not guaranteed to be thread safe.

I'm aware that you cannot run queries in parallel, and everything appears to be awaited properly. If I debug the program and step and inspect some of the data returned from EF then it works, probably because this forces the queries to complete.

EDIT If I place a breakpoint at the null check in the controller method and inspect the data of shipmentDetail the exception is NOT thrown.

Here's a snippit of the code:

Controller Method:

[Route("{id:int}/Deliveries")]
public async Task<ActionResult> DeliveryInfo(int id)
{
    var shipmentDetail = await db.ShipmentDetails.SingleOrDefaultAsync(s => s.Id == id);
    if (shipmentDetail == null)
        return HttpNotFound(string.Format("No shipment detail found with id {0}", id));
     var model = await DeliveryInfoModel.CreateModel(db, shipmentDetail);
    return View("DeliveryInfo", model);
}

CreateModel Method:

public static async Task<DeliveryInfoModel> CreateModel(Context db, ShipmentDetail shipment)
{
    DeliveryInfoModel model = new DeliveryInfoModel()
    {
        ShipmentInfo = shipment
    };

    //initialize processing dictionary
    Dictionary<int, bool> boxesProcessed = new Dictionary<int, bool>();
    List<DeliveryBoxStatus> statuses = new List<DeliveryBoxStatus>();

     for (int i = 1; i <= shipment.BoxCount; i++ )
        {
            boxesProcessed.Add(i, false);
        }

        //work backwards through process

        //check for dispositions from this shipment
        if(shipment.Dispositions.Count > 0)
        {
             foreach (var d in shipment.Dispositions)
            {
                DeliveryBoxStatus status = new DeliveryBoxStatus()
                {
                    BoxNumber = d.BoxNumber,
                    LastUpdated = d.Date,
                    Status = d.Type.GetDescription().ToUpper()
                };

                statuses.Add(status);
                boxesProcessed[d.BoxNumber] = true;
            }
        }

        //return if all boxes have been accounted for
        if (boxesProcessed.Count(kv => kv.Value) == shipment.BoxCount)
        {
            model.BoxStatuses = statuses;
            return model;
        }

        //check for deliveries
        if(shipment.Job_Detail.Count > 0)
        {
            foreach (var j in shipment.Job_Detail.SelectMany(d => d.DeliveryInfos))
            {
                DeliveryBoxStatus status = new DeliveryBoxStatus()
                {  
                    BoxNumber = j.BoxNumber,
                    LastUpdated = j.Job_Detail.To_Client.GetValueOrDefault(),
                    Status = "DELIVERED"
                };

                statuses.Add(status);
                boxesProcessed[j.BoxNumber] = true;
            }
        }

    //check for items still in processing & where
    foreach (int boxNum in boxesProcessed.Where(kv => !kv.Value).Select(kv => kv.Key))
    {
       //THIS LINE THROWS THE EXCEPTION
        var processInfo = await db.Processes.Where(p => p.Jobs__.Equals(shipment.Job.Job__, StringComparison.InvariantCultureIgnoreCase) && p.Shipment == shipment.ShipmentNum && p.Box == boxNum)
                                .OrderByDescending(p => p.date)
                                .FirstOrDefaultAsync();

       //process returned data
       //...
    }

    model.BoxStatuses = statuses;

    return model;
}

I'm not completely sure if it's because of the query made in the controller, or because of the queries made in the loop that aren't completing causing this behavior. Is there something I'm not understanding about when the queries are actually made/returned due to EF's laziness, or how async/await works in this situation? I have a lot of other methods & controllers that make async EF calls and haven't run into this previously.

EDIT

My context is injected into my controller using Ninject as my IoC container. Here's its config inside of NinjectWebCommon's RegisterServices method:

kernel.Bind<Context>().ToSelf().InRequestScope();
JNYRanger
  • 6,829
  • 12
  • 53
  • 81
  • I suspect your error is coming from somewhere else, this code should be fine. A) Is your `db` shared with other views/view models? B) Have you stepped through it with a debugger to see if it still throws? – CodingGorilla Jan 13 '16 at 17:53
  • A) No it's not being shared, it's injected to the controller and only used there, these are the only 2 operations using the `db` context for this http request. B) Using a debugger does not throw if I inspect the data returned from EF (I mentioned this in the question), otherwise it does. – JNYRanger Jan 13 '16 at 17:55
  • Can you expand on how it's injected? Is it injected from an IoC container? If so, how is your IoC registration configured? – CodingGorilla Jan 13 '16 at 17:56
  • I'm using Ninject as my IoC container. I'll add the config of the context to my question – JNYRanger Jan 13 '16 at 17:57
  • How is your `db` variable declared? Did you perhaps make it `static`? – Yuval Itzchakov Jan 13 '16 at 18:04
  • @YuvalItzchakov Not static, just private to the controller – JNYRanger Jan 13 '16 at 18:04
  • Is a new instance injected for each invocation of your action? – Yuval Itzchakov Jan 13 '16 at 18:06
  • @YuvalItzchakov Yes, it's per HTTP Request shown in the configuration in my edit above as per the [documentation for InRequestScope](https://github.com/ninject/Ninject.Web.Common/wiki/InRequestScope) – JNYRanger Jan 13 '16 at 18:08
  • From the looks of your code it seems to me that the only way to generate such exception is if that context is the same instance across multiple queries – Yuval Itzchakov Jan 13 '16 at 18:16
  • Post the full code of `CreateModel`. Perhaps there is an unintended async query being issued in the code you left out. – Brandon Jan 13 '16 at 18:20
  • @Brandon give me a minute i'll update. – JNYRanger Jan 13 '16 at 18:21
  • @Brandon I included everything up to the point of the exception. After that query I just access properties of the entity returned. I do not access any navigational properties at that point. – JNYRanger Jan 13 '16 at 18:25
  • Can we see the definition of ShipmentDetail please? – sellotape Jan 13 '16 at 18:30
  • @sellotape It's a generated class from EF using DatabaseFirst via diagram designer – JNYRanger Jan 13 '16 at 18:30
  • Is something in Processes lazy-loaded? Perhaps Jobs__? – sellotape Jan 13 '16 at 19:06
  • Also meant to add - what is lazy-loaded in ShipmentDetail? (I.e. virtual collections) – sellotape Jan 13 '16 at 19:08
  • @sellotape Nothing in Processes in lazy loaded. In ShipmentDetail the following are lazy loaded: `Dispositions`, `Job`, `Job_Detail`, and `Shipment` (unused in this method) – JNYRanger Jan 13 '16 at 19:13
  • Add the ones used in this method as .Include()'s to your query that gets ShipmentDetail. I'm pretty sure that will work and if so I'll add it as an answer. – sellotape Jan 13 '16 at 19:34
  • @sellotape Nope, that didn't make a difference in the behavior. – JNYRanger Jan 13 '16 at 19:42
  • Are you sure there is now *no* lazy loading going on in either query? With EF you need to ensure you're not lazy loading if you're doing async. – sellotape Jan 13 '16 at 19:47
  • @sellotape I'm positive that there's an Includes now for every navigational property (including nested ones) in the query and it has not changed the behavior. – JNYRanger Jan 13 '16 at 20:01
  • @sellotape Actually, you're correct. That was the issue. I just started going over everything again and I put the includes in the wrong controller method because I'm a dumbass. – JNYRanger Jan 13 '16 at 21:19
  • Good because I was at a loss after that!! I'll put something in an answer... – sellotape Jan 13 '16 at 21:22

1 Answers1

7

Avoid lazy loading when using async with Entity Framework. Instead, either load the data you need first, or use Include()'s to ensure the data you need is loaded with the query.

https://msdn.microsoft.com/en-gb/magazine/dn802603.aspx

Current State of Async Support

... Async support was added to Entity Framework (in the EntityFramework NuGet package) in version 6. You do have to be careful to avoid lazy loading when working asynchronously, though, because lazy loading is always performed synchronously. ...

(Emphasis mine)

Also:

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

sellotape
  • 8,034
  • 2
  • 26
  • 30