I have an .NET Core 3.1 app that serves an endpoint that describes health of application, and an IHostedService crunching through data in database.
There's a problem though, the worker function of HostedService starts processing for a long time, and as result the Configure()
method in Startup is not called and the /status
endpoint is not running.
I want the /status
endpoint to start running before the HostedService kicks off. How do i start the endpoint before the Hosted Service?
Sample code
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddHostedService<SomeHostedProcessDoingHeavyWork>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGet("/status", async context =>
{
await context.Response.WriteAsync("OK");
});
});
}
}
The HostedService
public class SomeHostedProcessDoingHeavyWork : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
await MethodThatRunsForSeveralMinutes();
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
private async Task MethodThatRunsForSeveralMinutes()
{
// Process data from db....
return;
}
}
I tried to explore adding the HostedService in Configure()
, but app.ApplicationServices
is a ServiceProvider hence readonly.
I ended up using Task.Yield()
and implementing an abstract class to encapsulate it, with optional PreExecuteAsyncInternal
hook and errorhandler ExecuteAsyncExceptionHandler
public abstract class AsyncBackgroundService : BackgroundService
{
protected ILogger _logger;
private readonly TimeSpan _delay;
protected AsyncBackgroundService(ILogger logger, TimeSpan delay)
{
_logger = logger;
_delay = delay;
}
public virtual Task PreExecuteAsyncInternal(CancellationToken stoppingToken)
{
// Override in derived class
return Task.CompletedTask;
}
public virtual void ExecuteAsyncExceptionHandler(Exception ex)
{
// Override in derived class
}
public abstract Task ExecuteAsyncInternal(CancellationToken stoppingToken);
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Prevent BackgroundService from locking before Startup.Configure()
await Task.Yield();
_logger.LogInformation("Running...");
await PreExecuteAsyncInternal(stoppingToken);
while (!stoppingToken.IsCancellationRequested)
{
try
{
await ExecuteAsyncInternal(stoppingToken);
await Task.Delay(_delay, stoppingToken);
}
catch (TaskCanceledException)
{
// Deliberate
break;
}
catch (Exception ex)
{
_logger.LogCritical($"Error executing {nameof(ExecuteAsyncInternal)} in {GetType().Name}", ex.InnerException);
ExecuteAsyncExceptionHandler(ex);
break;
}
}
_logger.LogInformation("Stopping...");
}
}