zzzzzz
zzzzzz

Reputation: 21

Async method: Second operation was started on this context before a previous operation completed

I'm new to Blazor and don't have much experience working with Tasks, so hopefully I'm just making a foolish mistake. I have an async method that is called via button press, but if the method is called again within 1-2 seconds I get the following exception.

Error: System.InvalidOperationException: A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext. For more information on how to avoid threading issues with DbContext, see https://go.microsoft.com/fwlink/?linkid=2097913.

This button is rendered for each row in a Users table. I'm trying to delete multiple user records in quick succession, but receive the above error.

Here is the code for the button press (using AntBlazor)

 <Button Type="primary" Danger OnClick="@(async() => await RemoveAsync(user))">Remove User</Button>

And here is the code for the RemoveAsync method.

private async Task RemoveAsync(User user)
{
   await UserService.UpdateUserAsync(user);
}

Am I misunderstanding how async/await works? or do I need to make use of Tasks to ensure the action is complete?

Edit:

Heres the UserService.UpdateUserAsync() code

public async Task<bool> UpdateUserAsync(User user)
{
   _appDBContext.Users.Update(user);
   await _appDBContext.SaveChangesAsync();
   return true;
}

Upvotes: 0

Views: 18378

Answers (1)

Henk Holterman
Henk Holterman

Reputation: 273179

Your code

public async Task<bool> UpdateUserAsync(User user)
{
   _appDBContext.Users.Update(user);
   await _appDBContext.SaveChangesAsync();
   return true;
}

I assume that _appDBContext is injected in the constructor, and that UserService itself is registered as Scoped.

That means that a single _appDBContext lives for the duration of your Form, accumulating tracking data. And because of async it runs the risk of being re-entered which is your direct problem.

One solution is not to inject a DbContext but a DbContextFactory.

And then it looks like:

public async Task<bool> UpdateUserAsync(User user)
{
   using var dbContext = _dbContextFactory.CreateDbContext(); 
   dBContext.Users.Update(user);
   var n = await dBContext.SaveChangesAsync();
   return n == 1; // just for being accurate
}

Now the context is scoped to each method. Much lighter on memory and you can have many overlapping operations.

Upvotes: 6

Related Questions