A simple helper to perform async application initialization and teardown for the generic host in .NET 6.0 or higher (e.g. in ASP.NET Core apps).
-
Install the Extensions.Hosting.AsyncInitialization NuGet package:
Command line:
dotnet add package Extensions.Hosting.AsyncInitialization
Package manager console:
Install-Package Extensions.Hosting.AsyncInitialization
-
Create a class (or several) that implements
IAsyncInitializer
. This class can depend on any registered service.public class MyAppInitializer : IAsyncInitializer { public MyAppInitializer(IFoo foo, IBar bar) { ... } public async Task InitializeAsync(CancellationToken cancellationToken) { // Initialization code here } }
-
Register your initializer(s) in the same place as other services:
services.AddAsyncInitializer<MyAppInitializer>();
-
In the
Program
class, replace the call tohost.RunAsync()
withhost.InitAndRunAsync()
:
public static async Task Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
await host.InitAndRunAsync();
}
This will run each initializer, in the order in which they were registered.
In addition to initialization, this library also supports performing cleanup tasks when the app terminates. To use this, make your initializer implement IAsyncTeardown
, and implement the TeardownAsync
method:
public class MyAppInitializer : IAsyncTeardown
{
public MyAppInitializer(IFoo foo, IBar bar)
{
...
}
public async Task InitializeAsync(CancellationToken cancellationToken)
{
// Initialization code here
}
public async Task TeardownAsync(CancellationToken cancellationToken)
{
// Cleanup code here
}
}
When you run the application with InitAndRunAsync
, each initializer that supports teardown will be invoked in reverse registration order, i.e.:
- initializer 1 performs initialization
- initializer 2 performs initialization
- application runs
- initializer 2 performs teardown
- initializer 1 performs teardown
When you create an initializer that also performs teardown, keep in mind that it will actually be resolved twice:
- once for initialization
- once for teardown
Initialization and teardown each runs in its own service provider scope. This means that a different instance of your initializer will be used for initialization and teardown, so your initializer cannot keep state between initialization and teardown, unless it's registered as a singleton.
If you do register your initializer as a singleton, keep in mind that it must not depend on any scoped service, otherwise the scoped service will live for the whole lifetime of the application; this is an anti-pattern known as captive dependency.
If, for some reason, you need more control over the application execution process, you can manually call the InitAsync
and TeardownAsync
methods on the host. If you do that, keep in mind that TeardownAsync
cannot be called after host.RunAsync()
completes, because the host will already have been disposed:
// DO NOT DO THIS
await host.InitAsync();
await host.RunAsync();
await host.TeardownAsync(); // Will fail because RunAsync disposed the host
So you will need to manually call StartAsync
and WaitForShutdownAsync
, and call TeardownAsync
before you dispose the host:
await using (var host = CreateHostBuilder(args).Build())
{
await host.InitAsync();
await host.StartAsync();
await host.WaitForShutdownAsync();
await host.TeardownAsync();
}
The InitAndRunAsync
, InitAsync
and TeardownAsync
all support passing a cancellation token to abort execution if needed. Cancellation will be propagated to the initializers.
In the following example, execution (including initialization, but not teardown) will be aborted when Ctrl + C
keys are pressed :
public static async Task Main(string[] args)
{
using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
// The following line will hook `Ctrl` + `C` to the cancellation token.
Console.CancelKeyPress += (source, args) => cancellationTokenSource.Cancel();
var host = CreateHostBuilder(args).Build();
await host.InitAndRunAsync(cancellationTokenSource.Token);
}
As mentioned above, when using InitAndRunAsync
, the cancellation token will not be passed to the teardown. This is because when your application is stopped, you typically still want the teardown to occur. Instead, teardown will run with a default timeout of 10 seconds (you can also specify a timeout explicitly).
If you don't want this behavior, you can manually call InitAsync
and TeardownAsync
as explained in the previous section.
If you were already using this library prior to version 3.x, your code would typically look like this:
await host.InitAsync();
await host.RunAsync();
This will still work without changes. Just keep in mind that, as explained in the previous section, adding a call to host.TeardownAsync()
after host.RunAsync()
will not work. If you need teardown, the simplest way is to remove the explicit call to InitAsync
, and call InitAndRunAsync
instead of RunAsync
.