Search code examples
c#exceptionasp.net-core-webapi.net-7.0asp.net-core-hosted-services

Requesting for application to stop during IHostedService.StartAsync results in an unhandled TaskCanceledException


I have an IHostedService which will use IHostApplicationLifetime.StopApplication if it fails during StartAsync. At first I thought I had missed out exception catching somewhere in my code when a TaskCanceledException popped up. After looking through things it turned out to not be my code but instead happen after control returns to the caller of the StartAsync somewhere in the belly of the WebApplication / Host framework.

WebApi net7.0
Dotnet version: 7.0.100

Exception in console:

Application is shutting down...
Unhandled exception. System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication`1 application, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
   at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.Run(IHost host)
   at Program.<Main>$(String[] args) in /.../Program.cs:line 29

The issue can easily be replicated:

  1. Create new WebApi
  2. Create IHostedService
  3. Add IHostedService as HostedService to IServiceCollection

In program.cs:

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHostedService<HostedService>();
...

HostedService.cs:

public class HostedService : IHostedService
{
    private readonly IHostApplicationLifetime _hostApplicationLifetime;

    public HostedService(IHostApplicationLifetime hostApplicationLifetime)
    {
        _hostApplicationLifetime = hostApplicationLifetime;
    }
    
    public Task StartAsync(CancellationToken cancellationToken)
    {
        _hostApplicationLifetime.StopApplication();
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

This does not seem intentional and it prevents graceful shutdown in the event that something goes wrong during startup.

Anybody who has some input to this? It seems like a bug


Solution

  • It seems that canceling/stopping the application during start is considered not a regular situation. One way to handle this is wait for full app startup and then signal to shutdown:

    public class HostedService : IHostedService
    {
        // ...
        public Task StartAsync(CancellationToken cancellationToken)
        {
            _hostApplicationLifetime.ApplicationStarted.Register(o => _hostApplicationLifetime.StopApplication(), null);
            return Task.CompletedTask;
        }
    }
    

    Another approach would be to just throw a custom exception and wrap app.Run into try-catch and swallowing this particular one.