Search code examples
async-awaitentity-framework-coreasp.net-core-webapimediatr

Weird Behavior while missing an await keyword generates error: Cannot access a disposed context instance


I recently ran into this error. I have never came across this before so wondering!

Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.
Object name: 'OrderDbContext'. 

The only thing i missed which produced this error is the await keyword in the controller action method before _mediator.Send(checkoutCommand); Once I added the await keyword this error vanished.

What (the heck) is wrong with missing this await keyword? The error does not explicitly state that. Can someone explain why missing an await keyword cause an error that database context is disposed?

Controller Action:

public async Task<IActionResult> Checkout(CheckoutOrderCommand checkoutCommand)
{
    **var id = _mediator.Send(checkoutCommand);**
    return CreatedAtRoute("Get Order by Id", id);
}

CQRS Mediatr Instructions

public class CheckoutOrderCommandHandler : IRequestHandler<CheckoutOrderCommand, int>
{
    private readonly IOrderUow _orderUow;
    private readonly IMapper _mapper;

    public CheckoutOrderCommandHandler(IOrderUow orderUow,
        IMapper mapper)
    {
        _orderUow = orderUow;
        _mapper = mapper;
    }


    public async Task<int> Handle(CheckoutOrderCommand request, CancellationToken cancellationToken)
    {
        var order = _mapper.Map<Order>(request);
        
        // Populate base entity fields
        order.CreatedBy = Constants.CurrentUser;
        
        // create new order
        var newOrder = await _orderUow.AddOrderAsync(order);
        
        return newOrder.Id;
    }
}

Unit of work implementation

public class OrderUow : IOrderUow
{
    private readonly OrderDbContext _orderContext;
    public OrderUow(OrderDbContext orderContext)
    {
        _orderContext = orderContext;
    }
    
    public async Task<Order> AddOrderAsync(Order order)
    {
        try
        {
            await _orderContext.Orders.AddAsync(order);
            await _orderContext.SaveChangesAsync();                
        }
        catch (Exception ex)
        {

        }
        return order;
    }
}

Solution

  • Missing an await without explicitly handling the task that is returned will mean that the code calling the asynchronous method will not create a resumption point and instead will continue executing to completion, in your case leading to the disposal of the DbContext.

    Asyncrhronous code is multi-threaded behind the scenes. Think of it this way, your web request enters on Thread #1 which creates a DbContext instance via an IoC container, calls an asynchronous method, then returns. When the code calls an async method, it automatically hands that code off to a worker thread to execute. By adding await you tell .Net to create a resume point to come back to. That may be the original calling thread, or a new worker thread, though the important thing is that the calling code will resume only after the async method completes. Without await, there is no resume point, so the calling code continues after the async method call. This can lead to all kinds of bad behaviour. The calling code can end up completing and disposing the DbContext (what you are seeing) or if it calls another operation against the DbContext you could end up with an exception complaining about access across multiple threads since a DbContext detects that and does not allow access across threads.

    You can observe the threading behaviour by inspecting Thread.CurrentThread.ManagedThreadId before the async method call, inside the async method, then after the async method call.

    Without the await you would see Thread #X before the method call, then Thread #Y inside the async method, then back to Thread #X after. While debugging it would most likely appear to work since the worker thread would likely finish by the time you were done with the breakpoints, but at runtime that worker thread (Y) would have been started, but the code after the call on thread X would continue running, ultimately disposing of your DbContext likely before Thread Y was finished executing. With the await call, you would likely see Thread #X before the method call, Thread #Y inside, then Thread #Z after the call. The breakpoint after the async call would only be triggered after the async method completes. Thread #X would be released while waiting for the async method, but the DbContext etc. wouldn't be disposed until the resumption point created by awaiting the async method had run. It is possible that the code can resume on the original thread (X) if that thread is available in the pool.

    This is a very good reason to follow the general naming convention for asynchronous methods to use the "Async" suffix for the method name. If your Mediator.Send method is async, consider naming it "SendAsync" to make missing await statements a lot more obvious. Also check those compiler warnings! If your project has a lot of warnings that are being ignored, this is a good reason to go through and clean them up so new warnings like this can be spotted quickly and fixed. This is one of the first things I do when starting with a new client is look at how many warnings the team has been ignoring and pointing out some of the nasty ones they weren't aware were lurking in the code base hidden by the noise.