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

Comments are closed.