Can I call an unawaited Async method and return a response while still using the DbContext?

Viewed 923

Is it possible in .net core for a controller to call an Async method that will use a scoped DbContext but not be awaited?

Basically I want this unawaited Async method to make an http call to an external dependency, and when done, save some information to the DB, but we don’t want to await the call because the user does not need the outcome of the operation, they just need to know that the operation was started.

When the call is not awaited, the DbContext is disposed of before the operation completes because the client http request (not the http call to the external dependency) is complete. What’s the best approach to handling this without awaiting the call?

2 Answers

There are several ways to do so. One would be to use a library like HangFire. Here is a good blog-post.

What you could also do is, to use an in-memory queue and write a hosted-service, that picks up the tasks. Within that hosted-service you can inject an instance of IServiceProvider and create a scope everytime you pick up an item from the queue.

Something like this for instance:

// BackgroundService is a base-class implementation of IHostedService
// You will find it in the package Microsoft.Extensions.Hosting.Abstractions
public class QueueWorkerService : BackgroundService
{
    private readonly IServiceProvider serviceProvider;
    // this could be anything you choose to fullfill the task!
    private readonly ISomeQueueProvider queueProvider;

    public QueueWorkerService(IServiceProvider serviceProvider, ISomeQueueProvider queueProvider)
    {
        this.serviceProvider = serviceProvider;
        this.queueProvider = queueProvider;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            if (!queueProvider.Tasks.Any())
            {
                await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
                continue;
            }

            using var scope = serviceProvider.CreateScope();

            var context = scope.ServiceProvider.GetRequiredService<MyDbContext>();

            foreach (var task in queueProvider.Tasks)
            {
                // do logic 
                await context.Set<SomeEntity>().AddAsync(someObject);
            }
        }
    }
}

Keep in mind this implementation is more like pseudo-code. For the queues you could really use anything.

Edit: Here is a sample for queues in worker processes from Microsoft.

No you can't. Going out of scope will dispose your DBContext. After your external call completes you have to create DBContext / scope again.

Depending on your need you can:

  • Check Hangfire or MediatR - both enable fire-and-forget events
  • Run simple task
Task.Run(async ()=>
{
   await MyApiCall();
   using (var scope = CreateDiScope())
   {
       var ctx = scope.ServiceProvider.GetService<MyDbContext>();
       ....
       ctx.SaveChanges()
   }
}

Related