How to run async tasks in ASP.NET Core? How to run asynchronous tasks when the application starts in ASP.NET Core? Sometimes, you need to perform a one-time initialization logic before the application can start normally. For example, you may want to verify that your configuration is correct, fill the cache or run a database migration. In this article, I will introduce the available options and show some simple methods and extension points that I think can solve the problem well.
Run async tasks in ASP.NET Core
I first describe the built-in solutions used to run synchronization tasks IStartupFilter
. Then, I walked through various options for running asynchronous tasks. You can (but probably shouldn’t) use IStartupFilter
or IApplicationLifetime
events to run asynchronous tasks. You can use this IHostedService
interface to run one-time tasks without preventing the application from launching. However, the only real solution is to manually run the tasks in program.cs . In my next article, I will show a suggested suggestion that makes this process a little easier.
Why do we need to run tasks when the application starts?
run async tasks in ASP.NET Core. Before the application can start and start processing requests, it usually needs to run various initialization codes. In ASP.NET Core applications, there are obviously many things that need to happen, such as:
- Determine the current hosting environment
- Load configuration from appsettings.json and environment variables
- Configuration of dependency injection container
- Build of dependency injection container
- Configuration of middleware pipeline
All these steps need to be performed to boot the application. However, in the
WebHost
run and start listening for requests before, usually to perform a single task. E.g: How to run async tasks in ASP.NET Core?
- Check if your strongly typed configuration is valid.
- Start/fill the cache with data from the database or API
- Run the database migration before starting the application. (This is usually not a good idea, but may be sufficient for some applications).
Sometimes these tasks do not have to be run before your application initiates a service request. For example, the cache startup example-if it performs well, it doesn’t matter whether the cache is queried before startup. On the other hand, you almost certainly want to migrate the database before the application starts processing requests! How to run async tasks in ASP.NET Core?
There are some examples that the ASP.NET Core framework itself requires a one-time initialization task. A good example is the data protection subsystem, which is used for instantaneous encryption (cookie values, anti-counterfeiting tokens, etc.). Before the application can start processing any requests, this subsystem must be initialized. To solve this problem, they use IStartupFilter
.
Run tasks synchronously IStartupFilter
I have written about this article before IStartupFilter
, because it is a very useful interface for customizing applications in your toolbar: How to run async tasks in ASP.NET Core?
- Explore IStartupFilter in ASP.NET Core (introduction
IStartupFilter
) - Know your middleware pipeline through the Middleware Analysis package (spoiler alert-use
IStartupFilter
) - Add validation to the strongly typed configuration object in ASP.NET Core (use again
IStartupFilter
)
If you are not familiar with filters, I recommend reading my introductory article, but here I will provide a short summary. How to run async tasks in ASP.NET Core?
IStartupFilter
s is executed in the process of configuring the middleware pipeline (usually done in Startup.Configure()
). They allow you to customize the middleware pipeline actually created by the application by inserting additional middleware, forking, or performing any other operation. For example, the following AutoRequestServicesStartupFilter
shows a new middleware inserted at the beginning of the pipeline:
public class AutoRequestServicesStartupFilter : IStartupFilter { public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) { return builder => { builder.UseMiddleware<RequestServicesContainerMiddleware>(); next(builder); }; } }
How to run async tasks in ASP.NET Core? code examle
This is useful, but what does it have to do with running a one-time task when the application starts?
The IStartupFilter
main function is to provide, after configuration setting, arranged after dependency injection container, but before the application is ready to start, can be hooked earlier application launch process. This means that you can use dependency injection with IStartupFilter
s, so you can run almost any code. This is DataProtectionStartupFilter
used, for example, to initialize a data protection system. I used a similar IStartupFilter
method to eagerly verify the strongly typed configuration.
Another very useful feature is that it allows you to add tasks to be performed by registering services in the DI container. This means that as a library author, you can register a task to run when the application starts, without the application author calling it explicitly. How to run async tasks in ASP.NET Core?
So, why can’t we just IStartupFilter
use it to run asynchronous tasks at startup?
The problem is that IStartupFilter
it is basically synchronous . The Configure()
method (you can see in the code above) does not return a Task
, so trying to synchronize via async is not a good idea. I’ll discuss it later, but now take a detour. How to run async tasks in ASP.NET Core?
Why not use health check?
ASP.NET Core 2.2 introduces a health check function for ASP.NET Core applications, which allows you to query the “health” of the application exposed through HTTP endpoints. After deployment, orchestration engines (such as Kubernetes) or reverse proxies (such as HAProxy and NGINX) can query this endpoint to check whether your application is ready to start receiving requests.
You can use the health check feature to ensure that your application does not start processing requests (that is, returning a “health” state from the health check endpoint) until all necessary one-time tasks are completed. How to run async tasks in ASP.NET Core? However, this has some disadvantages:
- The
WebHost
Kestrel itself will start before the one-time task has been performed. Although they will not receive “real” requests (only health check requests), this can still be a problem. - It introduces additional complexity. In addition to adding code to run a task, you also need to add a health check to test whether the task is complete and synchronize the status of the task.
- The startup of the application will still be delayed until all tasks are completed, so it is unlikely to reduce the startup time.
- If the task fails, the application will continue to run in a “dead” state, in which the health check will never pass. This may be acceptable, but personally, I prefer an application to fail immediately.
- The health check has not yet defined how to actually run the task, only whether the task is completed successfully. You still need to determine a mechanism for running tasks at startup.
To me, the health check does not seem to be suitable for one-time tasks. For some of the examples I describe, they may be useful, but I don’t think they are suitable for all situations. How to run async tasks in ASP.NET Core? I really want to be able to run a one-time task when the app starts, and then WebHost
run it again
Run async tasks
I spent a long time discussing all the methods that failed to achieve the goal and some solutions! In this section, How to run async tasks in ASP.NET Core? I will introduce some possibilities for running asynchronous tasks (that is, tasks that return a Task
and require await
-ing). Some are better than others, and some should be avoided, but I want to introduce the various options.
For specific discussion, I will consider a database migration example. In EF Core, you can migrate the database by calling at runtime myDbContext.Database.MigrateAsync()
, How to run async tasks in ASP.NET Core? which myDbContext
is an instance of the application DbContext
.
There is also a synchronized version of this method
How to run async tasks in ASP.NET Core?Database.Migrate()
, but it just pretends that it does not currently exist!
1. Use IStartupFilter
I have already introduced how IStartupFilter
to run synchronization tasks when the application starts . Unfortunately, the only way to run asynchronous tasks is to use the “asynchronous synchronous” method, which we call this method GetAwaiter().GetResult()
:
Warning: This code uses bad
async
practices.
public class MigratorStartupFilter: IStartupFilter { // We need to inject the IServiceProvider so we can create // the scoped service, MyDbContext private readonly IServiceProvider _serviceProvider; public MigratorStartupFilter(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next) { // Create a new scope to retrieve scoped services using(var scope = _seviceProvider.CreateScope()) { // Get the DbContext instance var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>(); //Do the migration by blocking the async call myDbContext.Database.MigrateAsync() .GetAwaiter() // Yuk! .GetResult(); // Yuk! } // don't modify the middleware pipeline return next; } }
How to run async tasks in ASP.NET Core? code examle
This will most likely not cause any problems-this code only runs when the application starts and then processes the request, so it seems unlikely that a deadlock will occur. How to run async tasks in ASP.NET Core? But frankly, I can’t say with certainty that I will avoid such code as much as possible.
David Fowler has compiled some good guides (in progress) to properly perform asynchronous programming. I strongly recommend you to read it!
2. Use IApplicationLifetime
events
I haven’t discussed much before, but IApplicationLifetime
you will receive notifications when your application is launched and closed through the interface. I will not go into details here, because for our purposes, it has some problems:
IApplicationLifetime
UseCancellationToken
s to register callbacks, which means you can only execute callbacks synchronously. Essentially, this means that no matter what action you take, you must insist on using asynchronous mode for synchronization.- The
ApplicationStarted
event is only the beginning after being raisedWebHost
, so the task application will run after accepting the request when it starts.
Given that they did not solve IStartupFilter
the asynchronous synchronization problem of s, nor did they prevent the application from starting, we will move IApplicationLifetime
on to the next possibility.
3. IHostedService
Used to run asynchronous tasks
IHostedService
Allow ASP.NET Core applications to perform long-running tasks in the background during the application life cycle. They have many different uses-you can use them to run periodic tasks on timers, handle other messaging paradigms, such as RabbitMQ messages or many other things. In ASP.NET Core 3.0, even ASP.NET Web hosting may be built on top of it IHostedService
.
It IHostedService
is asynchronous in nature, and has the StartAsync
and StopAsync
function at the same time . This is great for us, because it means there is no longer a need to synchronize through asynchrony! Implementing the database migrator as a managed service might look like this:
public class MigratorHostedService: IHostedService { // We need to inject the IServiceProvider so we can create // the scoped service, MyDbContext private readonly IServiceProvider _serviceProvider; public MigratorStartupFilter(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; } public async Task StartAsync(CancellationToken cancellationToken) { // Create a new scope to retrieve scoped services using(var scope = _seviceProvider.CreateScope()) { // Get the DbContext instance var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>(); //Do the migration asynchronously await myDbContext.Database.MigrateAsync(); } } public Task StopAsync(CancellationToken cancellationToken) { // noop return Task.CompletedTask; } }
How to run async tasks in ASP.NET Core? code examle
Unfortunately, IHostedService
this is not the panacea we hope for. It allows us to write truly asynchronous code, but it has two problems:
IHostedService
A typical implementation of s expects theStartAsync
function to return relatively quickly. For background services, it is expected that you will start the service asynchronously, but most of the work will be done outside of the startup code (see the example in the docs). Migrating the database “inline” is not such a problem, but it will prevent otherIHostedService
s from starting, which may or may not happen.IHostedService.StartAsync()
It is called post –WebHost
launch, so you can’t use this way to run tasks before your application starts.
The biggest problem is the second problem-the application will IHostedService
start accepting requests before running the database migration, which is not what we want. Back to the drawing board.
4. Run the task manually in Program.cs
None of the solutions shown so far provide a complete solution. They either need to use synchronization for asynchronous programming (although it may be good in the case of application startup, it is discouraged), or not prevent the application from starting. So far, there is a simple solution I have overlooked, and that is to stop trying to use the framework mechanism, but to complete the work by yourself.
The default Program.cs used in the ASP.NET Core template will generate and run an in statement IWebHost
in this Main
function:
public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); }
How to run async tasks in ASP.NET Core? code examle
However, after it is Build()
created WebHost
, but before you call it, nothing can stop you from running the code Run()
. Coupled with Main
the C#7.1 feature that allows your function to be asynchronous, we have a reasonable solution:
public class Program { public static async Task Main(string[] args) { IWebHost webHost = CreateWebHostBuilder(args).Build(); // Create a new scope using (var scope = webHost.Services.CreateScope()) { // Get the DbContext instance var myDbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>(); //Do the migration asynchronously await myDbContext.Database.MigrateAsync(); } // Run the WebHost, and start accepting requests // There's an async overload, so we may as well use it await webHost.RunAsync(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup<Startup>(); }
How to run async tasks in ASP.NET Core? code examle
This solution has many advantages:
- We are now performing real asynchrony, and there is no need to synchronize through asynchrony
- We can perform tasks asynchronously
- After the task is executed, the application accepts the request
- At this point, the DI container has been built, so we can use it to create services.
Unfortunately, this is not all good news. How to run async tasks in ASP.NET Core? We still lack a few things:
- Even if the DI container has been built, there is no middleware pipeline. This will not happen until you call
Run()
or goRunAsync()
upIWebHost
. At that point, the middleware pipeline is built,IStartupFilter
s is executed, and the application is started. If your asynchronous task needs to be configured in any of the following steps, then you are out of luck - By adding services to the DI container, we lose the ability to run tasks automatically. We must remember to run the task manually.
If these warnings are not the problem, then I think this last option will provide the best solution to the problem. In my next article, I will show some ways we can build on this basic example and make it easier to use.
Conclusion
In this article, How to run async tasks in ASP.NET Core? I discussed the need to run tasks asynchronously when the application starts. I described some of the challenges of doing this. For synchronous tasks, IStartupFilter
it provides a useful hook that can be connected to the startup process of ASP.NET Core applications, but running asynchronous tasks requires synchronization through asynchronous programming, which is usually a bad idea. I described many possible options for running asynchronous tasks, and the best way I found is to run the task “manually” in Program.cs, and do IWebHost
it between the building and running it. In the next article, I will provide some code to simplify the use of this pattern.