skip to Main Content

I have a simple web app, built with .NET Core (3.1) MVC and EF Core 5, containing a PersonController and a PersonService. (The project was provided by a teacher in my educational program, and he doesn’t understand why the problem occurs either.) I work with VS2019.

The PersonController contains a constructor initializing a PersonService, and a delete-method:

public PersonController(IPersonService personService)
{
    _PersonService = personService;
}

[HttpPost, ActionName("Delete")]
public async Task<IActionResult> DeleteConfirmed(int id)
{
    _ = _PersonService.DeleteAsync(id);

    TempData["Message"] = "successfully deleted";

    return RedirectToAction("Index");
}

The PersonService contains a DbContext and the DeleteAsync() method:

private readonly MyContext _context;

public async Task DeleteAsync(int? id)
{
    Person person = await _context.People.FindAsync(id);
    _context.Remove(person);
    await _context.SaveChangesAsync();
}

The problem:

The person is not removed from the database. When debugging, _PersonService.DeleteAsync(id) is called, the method executes until _context.Remove(person) but await _context.SaveChangesAsync() never gets executed. The execution returns to the calling method, and the TempData message shows up. The person-entry is not deleted from the database. This is the first time I’ve seen Visual Studio step out of a method before completion.

I can resolve this problem when I replace the Discard _ = with await. In this case, the code executes as expected — but I’ve read that "fire and forget" can be totally fine under the right circumstances.

I’ve also read that controllers are discarded after each request – but when the method in the PersonService stops executing, the debugger is still in the controller (at the TempData), so it seems that this can’t be the reason.

Can someone explain what’s causing the method to stop executing before it’s done? Or alternatively, maybe you could tell me how I can find out what’s happening in the background by using some feature of the debugger I’m unaware of, or something like that?

4

Answers


  1. You’re not awaiting the task so it won’t necessarily complete before the enclosing method does. The SaveChangesAsync() task doesn’t complete as it would block the current thread, so the enclosing method needs suspending for SaveChangesAsync() to complete before it ends.

    Consider replacing

     _ = _PersonService.DeleteAsync(id);
    

    with

    await _PersonService.DeleteAsync(id).ConfigureAwait(false);
    
    Login or Signup to reply.
  2. The reason why the method call doesn’t get executed is that you are using "fire and forget" by using the discard _ = instead of await. This means that the method will execute in the background without waiting for it to complete.

    Since the DeleteAsync() method is not awaited, the controller method will return a response to the client before the database operation is completed. This may cause the connection to the database to be closed before the operation is finished.

    It’s important to use await on method’s if you want them to finish.

    Login or Signup to reply.
  3. Other people answered what should be the correct way to handle this and that is to await your async methods.

    However, i believe what you are asking is why the person is not eventually deleted.

    The problem is not that the DeleteAsync() task in not completed, but rather that when the controller returns (while the task runs in the background probably still in the FindAsync() line) the service provider scope for this request is disposed/destroyed. When the task continues from the FindAsync(), deletes record (in memory) and tries to SaveAsync() it will result in an exception (since dbcontext is disposed) that will not be observed since no one is awaiting/observing the task.

    Login or Signup to reply.
  4. In your main method, you can configure your IPersonService as a singleton, because currently I believe IPersonService is configured as Scoped . By current configuration as Scoped, the IPersonService will be destroyed when the requested has been completed to the user, this the DeleteAsync is not properly completed. By implementing Singleton instead of Scope, the requested will continue event though the request already completed. Noted that this is just for academic purposes, and the best practices is still to use await instead of fire and forget

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search