Asynchronous programming
.NET Core Asynchronous C# Hosted Services Threading Visual Studio

Using Background Tasks with Hosted Services in .NET Core

Welcome to today’s post.

In this post I will discuss how to create background tasks as Hosted Services within a .NET Core application.

A hosted service is a special service within .NET Core that allows the segment of code to run as a separate background thread.

Before I get into showing how background tasks are used, I will explain what background tasks are.

What are Background Tasks?

A background tasks is a segment of code that runs in its own separate thread within a process. While the thread is running, it does not interrupt the running of the main thread. We could run multiple background threads within the main process and are limited only by our total memory and CPU.   

One drawback with running background threads is that if we are to run the code within the thread continuously, there would be extra demands on the CPU, which would take valuable CPU time from and affect the running of the main thread.

Background threads are the most useful then they run the segment of code for a fixed, usually short duration. This means that the CPU demand is fleeting and does only spikes for the duration of its execution. This has less impact on the main thread. Provided we use this mode of execution, running many background threads is possible without too much impact.

When to use Background Hosted Services

A background service can be hosted in the following container application types:

  • A console application.
  • A windows forms application.
  • A web application.
  • A web API application.
  • A server scheduled task application.
  • A windows service application.
  • An Azure Web Job under an Application Service (web application).

In what scenarios can we be expected to use background services?  There are many situations where we want to run a process in the background asynchronously and only be concerned with the result. These can include:

  • Creating a user account in a web application.
  • Processing order during checkout.
  • Scheduled database clean-ups.
  • Periodically archiving transactional data.
  • Disable or activate customer accounts.

Start a workflow to kick off a complex orchestration.

A basic architecture is shown below utilizing a timer service to trigger an API call within the background hosted service:

Background tasks

Implementation of an Application with a Background Service

I will show how to create a console application using .NET Core and run a background task running as a hosted service. The console application can be run as a scheduled task on a server or as a scheduled or triggered Azure web job.

I will show how to create a console application using .NET Core and run a background task running as a hosted service.

A console application can be created as a .NET Core application as shown:

Once created, the base application skeleton can be used as a basis for a console application that runs in the background to execute tasks in a background thread.

Later one, the logic can be shifted into a windows service or Azure web job

The decision to run the task as a long-running process or a short-running process will be determined by factors that can include:

  • Long duration, low frequency the task – maybe as a background thread task
  • Short job, higher frequency task – maybe as a scheduled task, job or serverless function.
  • The availability of computing power and cost.
  • Hosting costs.

The easiest host to run and debug is a console app. After our logic is tested thoroughly we can upgrade to one of the other hosted environments.  

The most commonly used background task is a timed background task that runs periodically and executes a task.

Implementation of the IHostedService Service

A background task is implemented from an IHostedService interface which as two methods

public Task StartAsync(CancellationToken cancellationToken)

public Task StopAsync(CancellationToken cancellationToken)

Note: Once the IHostedService is registered and the application run you do NOT need to run a loop (while, for etc.) within the StartAsync() method .. this will hang the thread!

When the task class is registered to the dependency injection configured services collection within ConfigureServices() and run, the task hosted service methods will run after the application initial setup completes. The StartAsync() method is executed to initiate the task. When the task ends the StopAsync() method is executed.

Registering the task for dependency injection is done as follows:

services.AddHostedService<TimedHostedService>();

The implementation of the hosted service is shown below:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using BookLoan.Services;
using BookLoan.Models;

namespace BackgroundTasksSample.Services
{
    internal class TimedHostedService : IHostedService, IDisposable
    {
        private readonly ILogger _logger;
        private Timer _timer;
        private readonly IBookService _bookService;

        public TimedHostedService(ILogger<TimedHostedService> logger,
            IBookService bookService)
        {
            _logger = logger;
            _bookService = bookService;
        }

        public Task StartAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation("Timed Background Service is starting.");

            _timer = new Timer(DoWork, null, TimeSpan.Zero, 
                TimeSpan.FromSeconds(60));

            return Task.CompletedTask;
        }

        private void DoWork(object state)
        {
            _logger.LogInformation("Timed Background Service is working.");

            List<BookViewModel> list =                
              _bookService.GetChangedBooksList()
              .GetAwaiter()
              .GetResult();

            int numberBooks = list.ToArray().Length;

            _logger.LogInformation(String.Format(
              "Number of new books {0}", numberBooks));
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            _logger.LogInformation(
              "Timed Background Service is stopping.");

            _timer?.Change(Timeout.Infinite, 0);

            return Task.CompletedTask;
        }

        public void Dispose()
        {
            _timer?.Dispose();
        }
    }
    #endregion
}

In the timer service we call the DoWork() method every 60 seconds and call a service method which executes a task (run a query to return a result, update data etc.)

Setup of the IHostedService Service

The console application main method including configuration of our hosted service dependency is shown below:

using System;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Configuration;
using System.IO;

using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Net.Http;

using Microsoft.Extensions.Http;
using Microsoft.Extensions.Options;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;

using BookLoan.Services;
using BookLoan.Helpers;
using BookLoan.Interfaces;
using BackgroundTasksSample.Services;

namespace BookLoanSignalRHubConsole
{
  public class Program
  {
    static void Main(string[] args)
    {
      var builder = new HostBuilder()
        .ConfigureAppConfiguration(
          (hostingContext, config) =>
          { 
            config.SetBasePath(Directory.GetCurrentDirectory());
            config.AddJsonFile("appsettings.json", true);
          })
          .ConfigureServices((hostingContext, services) =>
          {
            services.AddHttpClient();
            services.AddTransient<ApiServiceHelper, 
              ApiServiceHelper>();
            services.AddTransient<IApiServiceRetry, 
              ApiServiceRetry>();                                
            services.AddTransient<IApiServiceRetryWithDelay,  
              ApiServiceRetryWithDelay>();
            services.AddSingleton<IHttpContextAccessor, 
              HttpContextAccessor>();
            services.AddRouting();
            services.AddTransient<IBookService, BookService>();
            services.AddHostedService<TimedHostedService>();
          })
          .ConfigureLogging((hostingContext, logging) =>
          {
            logging.AddConfiguration(hostingContext.Configuration);
            logging.AddConsole();
            logging.AddDebug();
          });

          builder.UseConsoleLifetime();
          builder.Start();
        }
    }
}

To run the console in a loop we start the application hosted service using the UseConsoleLifetime() and Start() extension methods.

After running, the console should show with output informational and debug as shown:

This link details some more useful help on Hosted Services in .NET Core:

In future posts I will explore how to use background tasks within Windows Services in .NET Core.

That’s all for today’s post.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial