How to use BackgroundService in ASP.NET Core

You can use a hosted BackgroundService in ASP.NET Core for two purposes:

  • Run a long-running background task.
  • Run a periodic task in the background.

In this article, I’ll show how to create and register a hosted BackgroundService. In this example, it periodically pings a URL and logs the results.

1 – Subclass BackgroundService

The first step is to subclass BackgroundService:

  • Add the constructor with parameters for the dependencies.
  • Override the ExecuteAsync() method.
    • Add async to the method signature.

In this example, we’ll create a background pinger service. So here’s an example of how to subclass BackgroundService (we’ll implement ExecuteAsync() in the next step):

using Microsoft.Extensions.Hosting;
using System.Net.NetworkInformation;

public class PingerService : BackgroundService
{
	private readonly Ping Pinger;
	private readonly ILogger<PingerService> Logger;
	private readonly PingSettings PingSettings;
	public PingerService(ILogger<PingerService> logger, PingSettings pingSettings)
	{
		PingSettings = pingSettings;
		Pinger = new Ping();
		Logger = logger;
	}

	protected async override Task ExecuteAsync(CancellationToken stoppingToken)
	{
		//todo
	}
	
	public override void Dispose()
	{
		if (Pinger != null)
		{
			Pinger.Dispose();
		}
		base.Dispose();
	}
}
Code language: C# (cs)

For reference, here’s the PingSettings class:

public class PingSettings
{
	public TimeSpan Timeout { get; set; }
	public TimeSpan Frequency { get; set; }
	public string Target { get; set; }
}
Code language: C# (cs)

2 – Implement ExecuteAsync()

Here’s an example of implementing ExecuteAsync(). This is pinging a URL every X seconds in a loop forever (until the app shuts down):

protected async override Task ExecuteAsync(CancellationToken stoppingToken)
{
	while (!stoppingToken.IsCancellationRequested)
	{
		await Task.Delay(PingSettings.Frequency, stoppingToken);

		try
		{
			var pingTask = Pinger.SendPingAsync(PingSettings.Target, (int)PingSettings.Timeout.TotalMilliseconds);
			var cancelTask = Task.Delay(PingSettings.Timeout, stoppingToken);

			//double await so exceptions from either task will bubble up
			await await Task.WhenAny(pingTask, cancelTask);

			if (pingTask.IsCompletedSuccessfully)
			{
				var pingReply = pingTask.Result;
				Logger.LogInformation($"PingReply status={pingReply.Status} roundTripTime={pingReply.RoundtripTime}");
			}
			else
			{
				Logger.LogError("Ping didn't complete successfully");
			}

		}
		catch (Exception ex)
		{
			Logger.LogError(ex.Message);
		}
	}
}
Code language: C# (cs)

Note: Ping.SendPingAsync() doesn’t accept a CancellationToken. So I used the same technique I use to set a timeout for TcpClient: use Task.Delay() with the CancellationToken and use Task.WhenAny() to await both tasks.

Make sure to pay attention to the CancellationToken. This is canceled by the framework when the app is shutting down. This gives your BackgroundService a chance to perform a graceful shutdown.

3 – Register the BackgroundService and dependencies

In the ASP.NET Core initialization code, register the BackgroundService with AddHostedService(). Also register its dependencies. Here’s an example:

// rest of adding services

builder.Services.AddSingleton(new PingSettings()
{
    Timeout = TimeSpan.FromSeconds(5),
    Frequency = TimeSpan.FromSeconds(5),
    Target = "www.google.com"
});

builder.Services.AddHostedService<PingerService>();

//rest of init code
Code language: C# (cs)

Note: Before .NET 6, do this in Startup.ConfigureServices().

It has two dependencies – PingerSettings and ILogger<PingerService>. You don’t need to explicitly register ILogger<PingerService> (at least not in ASP.NET Core 6+).

When the app starts, it loads all hosted services and keeps them running in the background. It calls ExecuteAsync() right away. Be sure to await right away (with Task.Yield() if necessary) in order to not block startup.

4 – Run it

Launch the ASP.NET Core app. The background service starts running immediately. In this example, it’s pinging every 30 seconds and logging the results to the console log:

info: PingerService[0]
      PingReply status=Success roundTripTime=23
info: PingerService[0]
      PingReply status=Success roundTripTime=21
info: PingerService[0]
      PingReply status=Success roundTripTime=21

9 thoughts on “How to use BackgroundService in ASP.NET Core”

  1. Background service lifetimes depend on the App Pool’s “idle time out” setting. If your app pool has an “idle timeout” of 5 mins your service will stop after 5 mins and won’t restart. Therefore a request to the api in this example must be made within every 5 mins.

    Reply
    • Thanks David!

      When using IIS as the web server, it’s typically recommended to set your idle timeout to 0. This is especially true if you have a background service. In addition, be aware of app pool recycles. By default, IIS recycles your app pool every 29 hours. This attempts to do a graceful shutdown, which calls StopAsync() on all registered background services.

      So if you want your background service to try to do a graceful shutdown, you can override BackgroundService.StopAsync().

      Reply
      • Thanks David and Mak. Could not figure out why my background service kept stopping. Do not see any mention of this in the Hosted Service docs… But anyway, I set my idle time to 0 and my job is now running every 24hrs.

        Reply
          • 1. Open IIS Manager
            2. Click on Application Pools
            3. Right-click the relevant app pool > click Advanced Settings
            4. The setting is called Idle Time-out (minutes)

    • Yes, it’s suitable for long-running processes. It will stay running as long as the web app process is running.

      Note: Watch out for the web server recycling your web app. That will stop your background service.

      Reply
  2. A nice and simple but yet thorough enough explanation on how to use background services in ASP.NET. Have been reading quite some blog post the last few days in search for an answer on the lifetime of such background services and, while not in your blog post, I found it in de comments section.

    Thanks for your time and effort and for sharing this.

    Reply

Leave a Reply to Maclain Wiltzer Cancel reply