Search code examples
c#asp.net-coreasp.net-web-apifire-and-forget

Issue about calling an async method without awaiting in ASP.NET Core


Here we have a requirement that in the ASP.NET Core Web API controller, a job should be created and executed in the background, and the controller should reply the response immediately once the job starts running. There will be another controller API which could query the execution status of the job but that is a different topic.

To be simpler, the code is similar to the following:

public class Job
{
  public async Task RunAsync()
  {
    //...
  }
}

// API Controller code
private Job _job;

[HttpPost]
public async Task<IActionResult> CreateJob()
{
    _job.RunAsync(); // <-- No awaiting, making the job running in the background
    await DoAnotherThingAsync();
    return Ok();
}

I found that sometimes the running of this background job will be interrupted for some reasons - After this CreateJob API has returned, it looks like the _job.RunAsync() will not always completely finish its work - its execution thread could be killed at any time.

I know this is not a good implementation, but there are some questions:

  1. Why could this background job execution be interrupted?
  2. If I append await before _job.RunAsync(), this CreateJob API will eventually wait the background job to complete before it returns to the caller, which makes the job execution to be synchronous, right?
  3. Except adopting third party frameworks like Quartz.NET and Hangfire, is there any simple solution to this issue?

Solution

  • I found that sometimes the running of this background job will be interrupted for some reasons - After this CreateJob API has returned, it looks like the _job.RunAsync() will not always completely finish its work - its execution thread could be killed at any time.

    Yup.

    Why could this background job execution be interrupted?

    Any number of reasons! I explain several of them in my blog post (see the section "Shutdowns Are Normal"):

    • Rolling updates.
    • Applying OS or runtime patches.
    • Periodic restarts built-in to ASP.NET/IIS.

    In-memory work cannot survive application shutdown (and shutdowns are normal), so the only proper (reliable) solution is to store them somewhere durable (i.e., not in memory).

    If I append await before _job.RunAsync(), this CreateJob API will eventually wait the background job to complete before it returns to the caller, which makes the job execution to be synchronous, right?

    Well, that depends on what you mean by "synchronous".

    I would say that no, it's still asynchronous. With the await, the request thread is not blocked, so it's asynchronous.

    But: ASP.NET will (asynchronously) wait until the job completes before returning the HTTP response.

    Since ASP.NET doesn't return the HTTP response until the job is completed, then at a higher conceptual level you could say it was "synchronous".

    Except adopting third party frameworks like Quartz.NET and Hangfire, is there any simple solution to this issue?

    No.

    I have a blog series on this subject explaining why it's complex. Hangfire is about as simple of a solution as you're going to get.