Let's have a look and understand the relationship between IHostedService
and IHost
and how we can use them in our solutions.
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:
// 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
.