How to run background tasks in ASP.NET Core Application

In this blog post, I am going to walk through how to run background tasks in ASP.Net Core Web applications using infrastructure and API provided by the ASP.Net Core framework.

There are a lot of scenarios where you want to run a task continuously in the background. Often times, we create our own interface and classes and wire them up somehow in the Startup class to achieve this functionality.

In this blog post, I am going to cover two main ways of running background tasks in ASP.Net Core web applications. Both of the ways are provided out of the box by the ASP.Net core framework.

We do not need any external NuGet package for implementing these background tasks.

Background tasks in ASP.NET Core

Application for background tasks

To start this example, first of all I will create a new ASP.NET Core Web API Application.

Firstly, to do that, I will open up Visual Studio 2019. Once Visual Studio opens up, I will select the menu File -> New -> Project. This will open the Create a new Project project popup window.

Secondly, in the Create a new Project popup window, I will select ASP.NET Core Web Application from the project template and click on the Next button.

Thirdly, on the next page, I will provide the name of the application as BackgroundTask.Demo and click on the Create button.

Finally, on the final page, I will select the API template option. And I will keep other values default (ASP.Net Core 3.1) and click on the Create button.

Two ways of running Background tasks

As I mentioned earlier, we can run background tasks in ASP.NET Core in using two different constructs provided by the ASP.NET Core framework.

They are as follows:

  • Firstly, we can implement IHostedService interface
  • Secondly, we can derive from BackgroundService abstract base class

Background tasks using IHostedService

Now that the project is ready, it is time to create our first background tasks.

For creating a continuously running background task, let us consider we have a printing process. It will print an incrementing integer number.

To achieve this I will create a new class BackgroundPrinter. This class will implement the IHostedService interface.

The IHostedService interface provides two methods, StartAsync and StopAsync. The StartAsync method is where the task should be started. Whereas the StopAsync method is where we should implement logic while the task is stopped.

A couple of important point to remember about the StartAsync method:

  • Firstly, the StartAsync method is called by the framework before the Configure method of the Startup class is called
  • Secondly, the StartAsync method is called before the server is started

Finally, if the class implementing IHostedService uses any unmanaged object, the class has to implement IDisposable interface for disposing off the unmanaged objects.

IHostedService implementation with Timer object

For the first version of the code, I will implement a Timer inside the BackgroundPrinter class. And on a timer interval, I will print out the incremented number.

Firstly, I will declare a Timer object and an integer variable number in the class.

Secondly, inside the StartAsync I will create a new instance of the Timer.

Thirdly, for the Timer delegate, I will define an anonymous function, where I will increment and print the integer number using ILogger defined as a class-level variable.

Fourthly, I will set the timer to run in a 5-second interval.

Finally, I will implement the IDisposable interface and implement the Dispose method to call the Timer object’s Dispose method.

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Threading;
using System.Threading.Tasks;

namespace BackgroundTask.Demo
{
    public class BackgroundPrinter : IHostedService, IDisposable
    {
        private readonly ILogger<BackgroundPrinter> logger;
        private Timer timer;
        private int number;

        public BackgroundPrinter(ILogger<BackgroundPrinter> logger,
            IWorker worker)
        {
            this.logger = logger;
        }

        public void Dispose()
        {
            timer?.Dispose();
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            timer = new Timer(o => {
                Interlocked.Increment(ref number);
                logger.LogInformation($"Printing the worker number {number}");
            },
            null,
            TimeSpan.Zero,
            TimeSpan.FromSeconds(5));

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }
    }
}
Configuring the BackgroundPrinter class

Once the BackgroundPrinter class is ready, now we will configure it to the Dependency Injection container using the AddHostedService extension method on the IServiceCollection interface.

Now, we can either do it on the Startup class or on the Program class. I prefer doing it in the program class, just for clear separation.

Hence I am going to update the Program class to achieve this. In the CreateHostBuilder method, I will use the ConfigureServices extension method on the IHostBuilder interface to do this.

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace BackgroundTask.Demo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).ConfigureServices(services => 
                    services.AddHostedService<BackgroundPrinter>());
    }
}
Running the application

Now I am going to run the application to see the output in the console.

Once I run the application, I will be able to access the API endpoints. At the same time, the console will print the auto-incremented number every 5 seconds in the background.

background tasks in ASP.NET Core
Background task output

Dependency Injection with Background task

In a production scale application, we will probably not be using logic inside of the class that implements IHostedService. We will not do it so that we can have a separation of concerns and having responsibilities properly defines.

In most cases, the logic of what happens on the task will probably be encapsulated into its own class. Hence, it is very important that the dependency injection works with background tasks.

Now the good news is that since the background tasks are configured into the IServiceCollection, any types added to the dependency injection container will be available to the background task.

Worker Class

To demonstrate that I will create a class Worker and interface IWorker, which will have the logic of incrementing and printing the number. And the BackgroundPrinter class will just use the interface IWorker to execute the logic.

The IWorker interface will have a single method DoWork. The DoWork will take a single parameter, an instance of CancellationToken class.

For the worker class, inside the DoWork method, instead of running a timer, I will create a while loop. And the while loop will wait on the cancel request of the CancellationToken instance. The CancellationToken instance will be passed from the StartAsync method of BackgroundPrinter class.

Inside the while loop, I will increment the class level integer and print it out to the console using the ILogger. And at the end of the while loop, I will do a Task.Delay waiting 5 seconds before the loop executes again.

using System.Threading;
using System.Threading.Tasks;

namespace BackgroundTask.Demo
{
    public interface IWorker
    {
        Task DoWork(CancellationToken cancellationToken);
    }
}

using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace BackgroundTask.Demo
{
    public class Worker : IWorker
    {
        private readonly ILogger<Worker> logger;
        private int number = 0;

        public Worker(ILogger<Worker> logger)
        {
            this.logger = logger;
        }

        public async Task DoWork(CancellationToken cancellationToken)
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                Interlocked.Increment(ref number);
                logger.LogInformation($"Worker printing number {number}");
                await Task.Delay(1000 * 5);
            }
        }
    }
}

Once the Worker class is ready, I will update the BackgroundPrinter class to use the IWorker interface for the business logic.

Apart from injection ILogger, now I will inject IWorker as well in the constructor of the BackgroundPrinter class.

using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Threading;
using System.Threading.Tasks;

namespace BackgroundTask.Demo
{
    public class BackgroundPrinter : IHostedService
    {
        private readonly ILogger<BackgroundPrinter> logger;
        private readonly IWorker worker;

        public BackgroundPrinter(ILogger<BackgroundPrinter> logger,
            IWorker worker)
        {
            this.logger = logger;
            this.worker = worker;
        }

        public async Task StartAsync(CancellationToken cancellationToken)
        {
            await worker.DoWork(cancellationToken);
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            return Task.CompletedTask;
        }
    }
}

Finally, I will register the Worker class in the dependency injection container inside of the Startup class. I will update the ConfigureServices method to add the Worker class as a singleton instance to the dependency injection container.

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace BackgroundTask.Demo
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers();

            services.AddSingleton<IWorker, Worker>();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

I will not make any changes to the Program class, since we are still using BackgroundPrinter class for the background task. Now if I run the application, I will see the exact same response in the console as before.

Background tasks using BackgroundService

Using BackgroundService abstract base class is the second way of running background tasks. The implementation of BackgroundService is relatively simpler compared to IHostedService. But at the same time, you have lesser control over how to start and stop the task.

To demonstrate how BackgroundService abstract base class works, I will create a new class DerivedBackgroundPrinter. This new class will drive from the BackgroundService class.

And this time, I will just use the IWorker interface for the business logic. Hence, I will create a constructor dependency on IWorker interface. Since the IWorker interface is already configured in the dependency injection container, it will automatically be passed along to this class.

The BackgroundService abstract base class provides a single abstract method ExecuteAsync. We will have our implementation to call the DoWork of the IWorker interface inside of this ExecuteAsync method.

using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;

namespace BackgroundTask.Demo
{
    public class DerivedBackgroundPrinter : BackgroundService
    {
        private readonly IWorker worker;

        public DerivedBackgroundPrinter(IWorker worker)
        {
            this.worker = worker;
        }

        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
        {
            await worker.DoWork(stoppingToken);    
        }
    }
}

Once this class is ready, I will update the Program class, to use DerivedBackgroundPrinter class instead of BackgroundPrinter as a background task runner.

Hence, inside of the CreateHostBuilder method of the Program class, I will replace the BackgroundPrinter with DerivedBackgroundPrinter for the AddHostedService generic extension method call.

using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace BackgroundTask.Demo
{
    public class Program
    {
        public static void Main(string[] args)
        {
            CreateHostBuilder(args).Build().Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                }).ConfigureServices(services => 
                    services.AddHostedService<DerivedBackgroundPrinter>());
    }
}

Now if I run the application, I will see the exact same response as before.

Conclusion

As you can see it is extremely simple to create background tasks using ASP.NET Core provided IHostedService interface as well as BackgroundService abstract base class. The best part is that there is no external NuGet package dependency.

The source code for this blog is available in my GitHub repository here.

The entire coding session is also available in my YouTube channel here.