Control ASP.NET behavior when background service crashes

When a background service throws an unhandled exception in ExecuteAsync(), you have two options:

  • Ignore the exception. This is the default before .NET 6.
  • Stop the host application with a graceful shutdown. This is the default in .NET 6+.

You can change the behavior if the default isn’t what you want (notice the default behavior changed in .NET 6).

In .NET 6+, you can configure the behavior in the initialization code. Here’s an example of making it ignore crashed background services:

var builder = WebApplication.CreateBuilder(args);
//Add services to the container.

//rest of code not shown for brevity

builder.Services.Configure<HostOptions>(opts => 
    opts.BackgroundServiceExceptionBehavior = BackgroundServiceExceptionBehavior.Ignore);

var app = builder.Build();
Code language: C# (cs)

This setting didn’t exist before .NET 6, and the default behavior was to ignore the crashing background service. If you wanted to stop the application, you’d have to do it yourself. I’ll show how to do that next.

Stop the host application when a background service crashes (before .NET 6)

To stop a host application from a crashing background service before .NET 6:

  • Catch the unhandled exception in ExecuteAsync().
  • Call IHostApplicationLifetime.StopApplication().

Here’s an example:

public class DatabaseLoggerService : BackgroundService
{
	private IHostApplicationLifetime HostApplicationLifetime;
	public DatabaseLoggerService(IHostApplicationLifetime hostApplicationLifetime)
	{
		HostApplicationLifetime = hostApplicationLifetime;
	}

	protected async override Task ExecuteAsync(CancellationToken stoppingToken)
	{
		while(!stoppingToken.IsCancellationRequested)
		{
			try
			{
				//Execute the important background activity in a try/catch
				await BulkInsert(stoppingToken);
			}
			catch(TaskCanceledException canceledEx)
			{
				//Service was stopped, so exit the loop
				return;
			}
			catch (Exception fatalException)
			{
				//Handle fatal exceptions appropriately based on your requirements
				//Then initiate a graceful shutdown
				HostApplicationLifetime.StopApplication();
				return;
			}
		}
	}
	//rest of class
}
Code language: C# (cs)

Note: After initiating the shutdown, exit ExecuteAsync() with ‘return’ or ‘throw’. StopApplication() will eventually stop the background service, but it’s not always immediate. This means the loop conditions could still be true, and the loop could continue to execute. Don’t let this be a possibility, exit the method explicitly.

Calling HostApplicationLifetime.StopApplication() initiates a graceful shutdown. The framework will call StopAsync() in all background services you have running.

Don’t call Environment.Exit(). It may appear that it’s doing a graceful shutdown, but it’s not. If you use that, you may run into unexpected problems. Use IHostApplicationLifetime.StopApplication() instead.

Dependency inject IHostApplicationLifetime

To be able to use IHostApplicationLifetime in the background service, add it as a constructor parameter:

public DatabaseLoggerService(IHostApplicationLifetime hostApplicationLifetime)
Code language: C# (cs)

The framework will resolve this dependency and pass it into your background service.

Note: You can manually resolve the dependency if necessary (such as if you’re injecting the background service into the controllers).

Leave a Comment