2

I am building an ASP.NET Web API (.NET 5). Other than having typical controller, I also want to have a background job running parallel all the time. For this purpose, I have created a hosted service:

public class MyHostedService : IHostedService
{
    private readonly IHostApplicationLifetime _appLifetime;
    private readonly ILogger _logger;

    public MyHostedService(IHostApplicationLifetime appLifetime, ILogger<MyHostedService> logger)
    {
        _appLifetime = appLifetime ?? throw new ArgumentNullException(nameof(appLifetime));
        _logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {             
        return Task.Factory.StartNew(async () =>
        {
            await Run(cancellationToken);
        }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Default);
    }

    private async Task Run(CancellationToken cancellationToken)
    {
        try
        {
            // some long running code
        }
        catch (OperationCanceledException)
        {
            _logger.LogInformation("Application was terminated from the outside");
            _appLifetime.StopApplication();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Unhandled exception!");
            _appLifetime.StopApplication();
        }
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        _logger.LogDebug("StopAsync executes");
        return Task.CompletedTask;
    }
}

I registered it in ConfigureServices:

public void ConfigureServices(IServiceCollection services)
{
  services.AddControllers();
  services.AddSwaggerGen(c =>
  {
      c.SwaggerDoc("v1", new OpenApiInfo {Title = "My API", Version = "v1"});
  });

  services.AddHostedService<CacheManagerHostedService>();
}

The API works, and the MyHostedService instance runs properly. The code in StartAsync spins up a new thread. If I don't do that, the API server would not start listening at all. I noticed that the app closes almost immediately when I press CTRL+C. The log in the StopAsync is never written. I'd like to be able to run some cleanup in StopAsync later on. What should I change?

mnj
  • 2,539
  • 3
  • 29
  • 58
  • 1
    https://pastebin.com/MU5mjaWQ -> I get both logs if I run the app on Windows using Kestrel. How are you running this? – Camilo Terevinto Oct 10 '21 at 18:25
  • In particular, Docker can interfere with shutdown signals. – Stephen Cleary Oct 10 '21 at 18:31
  • I'm running this on Linux with `dotnet run` – mnj Oct 10 '21 at 19:04
  • An update - I don't know how that happened, but without any changes, it just started to work as it should. I've been replacing code with @CamiloTerevinto example, then undoing it. Works... That's weird. Anyway, the question I asked was just one of my worries. Another one is, I wonder why the try-catch does not work in the new thread when I do CTRL+C. No exception is catched. – mnj Oct 10 '21 at 20:02
  • 1
    @Loreno Try switching to `Task.Run(Run(cancellationToken))` (or `Task.Run(() => Run(cancellationToken))`, don't remember right now), using `Task.Factory.StartNew` is discouraged and using `async`/`await` for the lambda might be the cause of the issue. – Camilo Terevinto Oct 10 '21 at 20:27
  • Doesn't make any difference. I'm using Rider for C# on Ubuntu 20.10 LTS. StopAsync() is never called when Ctrl+C is pressed. I have to perform cleanup and it just can't be done. – Simple Fellow Nov 07 '21 at 14:18

0 Answers0