简体   繁体   中英

Why Does Await Not Appear to Prevent Second Operation on EF Context

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();

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM