ASP.NET Core background processing with IHostedService

Run background processes with the IHostedService and how to inject your services with dependency injection

Many services need background processing. The ASP.NET Core 2.X IHostedService interface gives you an easy implementation skeleton to implement background processes. The Hosted Services are registered in the dependency injection at startup and started automatically. You do not have to do the pluming to get them started at startup. On shutdown you can implement a graceful shutdown. When running background processes there a few pitfalls to avoid. In this blog I’ll introduce the IHostedService and how to avoid common memory leaks when implementing the hosted service.

Using IHostedService
When implementing the IHostedService interface, you have two methods to implement: StartAsync() and StopAsync(). The StartAsync is called at startup and the StopAsync is called at shutdown. The implementation of the class will inject the dependencies needed to run your business logic.

public interface IHostedService
{
    //
    // Summary:
    //     Triggered when the application host is ready to start the service.
    Task StartAsync(CancellationToken cancellationToken);
    //
    // Summary:
    //     Triggered when the application host is performing a graceful shutdown.
    Task StopAsync(CancellationToken cancellationToken);
}

An implementation of the IHostedService interface can be added in startup.cs service registration:

public void ConfigureServices(IServiceCollection services)
{
    ...
    services.AddHostedService(); 
    //ASP.NET 2.0 : //services.AddSingleton();
}

When the service is registered, it will be initialized when startup is finished. You implementation of the StartAsync is called where you can start processing.

Start repeating process in IHostedService
A common pattern for background tasks is:

  • Run your logic
  • Wait some time
  • Check if you have to stop or repeat the process

A simple implementation of a base class (inspired by a sample of David Fowler) which takes care of the plumbing can be:

public abstract class BackgroundService : IHostedService
{
    private Task _executingTask;
    private readonly CancellationTokenSource _stoppingCts =
                                                   new CancellationTokenSource();

    public virtual Task StartAsync(CancellationToken cancellationToken)
    {
        // Store the task we're executing
        _executingTask = ExecuteAsync(_stoppingCts.Token);

        // If the task is completed then return it,
        // this will bubble cancellation and failure to the caller
        if (_executingTask.IsCompleted)
        {
            return _executingTask;
        }

        // Otherwise it's running
        return Task.CompletedTask;
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken)
    {
        // Stop called without start
        if (_executingTask == null)
        {
            return;
        }

        try
        {
            // Signal cancellation to the executing method
            _stoppingCts.Cancel();
        }
        finally
        {
            // Wait until the task completes or the stop token triggers
            await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite,
                                                      cancellationToken));
        }
    }

    protected virtual async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        //stoppingToken.Register(() =>
        //        _logger.LogDebug($" GracePeriod background task is stopping."));

        do
        {
            await Process();

            await Task.Delay(5000, stoppingToken); //5 seconds delay
        }
        while (!stoppingToken.IsCancellationRequested);
    }

    protected abstract Task Process();
}

This implementation handles a graceful shutdown of the background task. You only have to implement the Process() method to get your work done.

Note that when you leave out the IServiceScopeFactory and only use the IService provider you will create a memory leak if you create any IDisposable objects. These objects are referenced in the Dependency injection scope and only released when your programs ends. Even when you dispose them, the Garbage Collector will not collect them.

Dependency injection into your IHostedServices

The IHostedService implementations are singletons in you application. When injecting a dependency that is scoped or transient they will have the same lifespan as IHostedService. This is probably not the intention of the scoped or transient object. Fortunately when running in development mode, the dependency injector will stop you from running. It checks if the dependency is scoped and then throws an exception.

System.InvalidOperationException: Cannot consume scoped service ‘MyDbContext’ from singleton ‘IMySingleton’.

The error prevents you from running a scoped object (for example a DbContext) in a singleton. To come around this you can use the IServiceScopeFactory create an own scope for each time you are running your process. The scope takes care of all scoped and IDisposable objects created for your processing. The code for this can look like:

    public abstract class ScopedProcessor : BackgroundService
    {
        private readonly IServiceScopeFactory _serviceScopeFactory;

        public ScopedProcessor(IServiceScopeFactory serviceScopeFactory) : base()
        {
            _serviceScopeFactory = serviceScopeFactory;
        }

        protected override async Task Process()
        {
            using (var scope = _serviceScopeFactory.CreateScope())
            {
                await ProcessInScope(scope.ServiceProvider);
            }
        }

        public abstract Task ProcessInScope(IServiceProvider serviceProvider);
    }

In the method ProcessInScope you can create you processing logic object with the serviceProvider. No need for injection and all logic objects are fresh on each processing cycle.

     var processor = serviceProvider.GetService();
     ...

Related posts
Schedule services
Headless services
Using scoped services
Using HttpClientFactory

Final thoughts
The IHostedServices gives you a simple way of implementing background services. The graceful shutdown is very useful. Keep in mind that you manage non singleton objects that are created by the dependency injector with a scope.

In the blog post Run scheduled background tasks in ASP.NET Core you can read more on how to do scheduling of background tasks.

A working demo of a background process/scheduled background process can be found in the following git repository Demo code background processing with IHostedService.

Advertisements

19 thoughts on “ASP.NET Core background processing with IHostedService”

  1. services.AddSingleton(); needs to be services.AddSingleton(); or services.AddSingleton(); right? How can I inject my DbContext into the derived class of ScopedProcessor?

    Like

  2. Just came across BackgroundService in .NET Core 2.1. I can’t understand when ExecuteAsync() is ever called. I create a class based off of BackgroundService, do an AddService(), and I can see StartAsync() and StopAsync() being called, but never ExecuteAsync(). I can inject MyService into a controller but still nothing. Am I misunderstanding something?

    Like

  3. Looks to me like the StartAsync method won’t return until we hit an await in the Process method. Is this right? I would have thought you’d just want to kick the loop off and then consider the BackgroundService started. How can this be written to acheive this?

    Ps. BackgroundService class is now available as part of .net core 2.1 in Microsoft.Extensions.Hosting

    Like

    1. I also saw the behavior where the StartAsync method wouldn’t return. As VS warns, if there are no awaitable calls in ExecuteAsync, then it will execute synchronously.

      In my application, this is the case. To resolve, I made the following changes:

      In StartAsync…
      _executingTask = Task.Run(() => Execute(_stoppingCts.Token));

      And I changed ExecuteAsync signature to…
      protected virtual void Execute(CancellationToken stoppingToken)

      Like

    1. Yes you can. However, I would advise using something different than a delay of 24 hours. When you have a process restart, the next run moves 24 hours. Schedule on specific times or use a queue where you can use an invisible time interval to delay the processing.

      Like

  4. Hi Peter, thank you for the writeup. I’m using this BackgroundService method to run a periodic task in my asp.net application. The problem I’m having is that my service must access my SQL database using EF Core. I can properly inject the IServiceScopeFactory and can grab my DbConext from that each time my service kicks off, but it seems nothing else in my program can successfully modify the database during that processing time. If my service, for example, grabs data from the database and performs processing for 10 seconds before saving back to the DB using SaveChanges(), and during that 10s of processing, an API controller gets a request to modify something in the database, it will make the change, save it, and then return to the caller. But then when my long-running periodic task finishes its 10s of processing, it saves ITS data to the database, which overwrites what the API controller wrote. I would have thought EF Core would be smart enough to know which columns in the model were newer than others, but it’s overwriting the whole thing. The API controller may only change “User.Name” and the periodic task may only change “User.DateOfBirth” but it still clobbers the Name value that was written by the API controller. Is there a different way I need to be accessing the DbContext in both my API Controller and BackgroundService that allows for each to monitor the other’s Change Tracking? Thanks again.

    Like

    1. It seems you have a number of design issues like processing time of your transaction and your read/write locking strategy on concurrency in EF. Without knowing what your specifications are, I would say you have a problem if you want to scale your service. When scaling your service, processes will independently read and write data. And because you haven’t thought of an locking strategy for your data, the last write wins (this can be a valid solution). When defining your locking strategy you accept some scenarios to fail or accept that data can be overridden (like what is happening now). You can find more on locking and how to implement is: https://www.google.com/search?q=persimistic+locking+ef+core&ie=&oe=. If choosing for Pessimistic Concurrency the above scenario will throw an exception if the data is changed while processing. Then you should look at why you are taking 10 seconds for processing the data. I would look if this can be done in multiple smaller transaction. When a concurrency problem happens you can just redo you small transaction. Your error handling is a lot easier and will scale a lot better.

      The DbContext is not designed to be used in multiple threads (that is why it is scoped for the dependency injection). You should let is live as short as possible. I would suggest to create a new one for each unit of work in your background process. That will make processing error handling a lot easier.

      Like

      1. Hi Peter, thanks for the detailed reply. I think you’re right about needing to refactor the logic to use small transactions. I can’t change the 10s processing time, but I can store local copies of what values needs to be updated in the entities, then do a Reload() on each entity before I set those update values and immediately call SaveChanges(). That way, the time of fetching the data, modifying it, and saving it again is as small as possible and follows the UnitOfWork pattern. Thanks for pointing me in the right direction!

        Like

  5. This doesn’t work for me.
    var hcp = serviceProvider.GetRequiredService();
    hcp.HttpContext IS ALWAYS NULL

    HttpContext IS ALWAYS NULL
    public MyTask(ILogger logger, IHttpContextAccessor httpContextAccessor,
    IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
    {
    _logger = logger;
    _httpContextAccessor = httpContextAccessor;
    // httpContextAccessor.HttpContext IS ALWAYS NULL
    }

    Like

  6. Hi Peter,

    This is what I have in my scopedProcessor.

    using (var scope = _serviceScopeFactory.CreateScope())
    {
    var memberScoped = scope.ServiceProvider.GetService();
    var subScope = scope.ServiceProvider.GetService();
    var emailScope = scope.ServiceProvider.GetService();

    await ProcessInScope(scope.ServiceProvider);
    }

    I still get the “Cannot consume scoped service ‘Membership.Core.Repository.IRepository`1[Membership.Core.Models.Subscription]’ from singleton ‘Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor’.” error. is there anything I’m doing wrong?

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.