23 July 2020 ~ 4 min read

IHostedService in .NET Core


Let's have a look and understand the relationship between IHostedService and IHost and how we can use them in our solutions.

Hosted Services class diagram
Hosted Services class diagram

IHost

The responsibility of IHost is to manage the lifetime of any IHostedService. This means it handles the start and stop actions of as many IHostServices it knows about. Any hosted services live and die with the host. It is also configurable in the ways we've come to expect such as logging and dependency injection. The simplest way to build a host is:

IHost host = Host.CreateDefaultBuilder().Build();

IHost and IHostedService both have a similar lifecycle. The difference between them being a CancellationToken is required on IHostedService. The Host instance supplies the token to any IHostedService it has registered to it.

IHostedService

IHostedService allows you to create a async tasks with start and stop actions. It's lifetime is owned by the IHost but the handling of the start and stop actions is up to the hosted service and developer.

public class MyHostedService : IHostedService
{
  public async Task StartAsync(CancellationToken cancellationToken)
  {
    // ...
  }

  public async Task StopAsync(CancellationToken cancellationToken)
  {
    // ...
  }
}

Background service

BackgroundService is an implementation of IHostedService for any long running IHostedService. It runs the ExecuteAsync() task within the StartAsync() task of the hosted service.

public class MyBackgroundService: BackgroundService
{
  protected override async Task ExecuteAsync(CancellationToken stoppingToken)
  {
    // ...
  }
}

IHostBuilder

IHostBuilder lets you configure and build a IHost. The method ConfigureServices is additive so you can add as many different hosted services as you want and as many instances of the same hosted service.

public static IHostBuilder CreateHostBuilder(string[] args) {

  return Host.CreateDefaultBuilder(args)
    .UseServiceProviderFactory(new AutofacServiceProviderFactory())
    .ConfigureServices((hostContext, services) =>
    {
        // Adds services to the container
        services.AddHostedService<MyHostedService>();
        services.AddHostedService<MyHostedService>();
        services.AddHostedService<MyBackgroundService>();
    })
    .ConfigureContainer<ContainerBuilder>((hostContext, builder) =>
    {
        //Enables configuring the instantiated dependency container.
    });
}

Build and run the IHost

await CreateHostBuilder(args).Build().RunAsync();

Application solutions

A scenario might be that you need to handle a user action to your API which might be a long wait e.g. transforming large amounts of data and aggregation work items. This type of problem is usually solved with a BackgroundService.

From there you can decide if you want to register it to an existing host or a new one. To add your hosted service to an existing Web API application, it needs to implement IWebHost. You can register your hosted service in Startup.cs. With this the hosted service will normally start and stop within the life time of the Web API. You can also then share state between API and hosted service e.g. sharing an in memory queue.

Another option is to run it in a completely separate process. To do this you would create a new program, for example, using the .NET Core Worker Service template which is configured to run in its own IHost. These type of solutions usually poll a database or process messages from a message bus. With this option we can also consider scaling up and down if deployed as a container to something like Kubernetes.

Other simpler scenarios might mean you want to manage the lifetime of smaller task e.g. a message bus connection.

Async and Sync Main()

Since C# 7.1, we have support for async on Main() methods. The IHost implementation of Host is purely asynchronous but sometimes we want to run in a synchronous way.

To run synchonrously ("async over sync"), there's an method available through an extension so we can perform the following:

IHost extension code

// Program.cs
public static void Main(string[] args)
{
   CreateHostBuilder(args).Build().Run();
}

Under the hood, the Run() method calls host.RunAsync().GetAwaiter().GetResult();. Any specific exceptions that have occurred in the task are thrown. Alternatively, we can run in an asynchronous way:

// Program.cs
public static async Task Main(string[] args)
{
   await CreateHostBuilder(args).Build().RunAsync();
}

In the above example if an exception occurred in an IHostedService the above task would also have the exception attached to it.

Beware that exceptions thrown in a hosted service running on a separate thread, won't be by the host. You should catch and handle any exceptions in the hosted service e.g. in ExecuteAsync() if you're using BackgroundService.


Headshot of Jason Watson

Hi, I'm Jason. I'm a software engineer and architect. You can follow me on Twitter, see some of my work on GitHub, or read more about me on my website.