I'm trying to build a service into my .NET Core 3.1 application that runs hourly. My service runs each hour (i.e. hits a breakpoint in StartAsync()
), but the implementation throws the following error immediately:
A task was canceled
I've searched quite a bit, however I'm having difficulty finding helpful responses; it seems these are most commonly thrown when using HttpClient
, which is not relevant to my case.
That said, I have a hunch that I should be awaiting something, but my attempts to add await
resulted in the application hanging on startup (I imagine I was unintentionally awaiting the full hourly iteration...?). The tutorial I've been following does not use await
anywhere; barring an oversight (very possible), my code seems identical to the example shown.
I've pulled out the guts of ExecuteAll()
to confirm the code within works.
Thanks.
public Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan interval = TimeSpan.FromHours(1);
var nextRunTime = DateTime.Today.AddHours(DateTime.UtcNow.Hour + 1);
var curTime = DateTime.UtcNow;
var firstInterval = nextRunTime.Subtract(curTime);
//Run once on startup
TriggerHourlyService(null);
void action()
{
//Schedule the first iteration
var t1 = Task.Delay(firstInterval);
t1.Wait();
TriggerHourlyService(null);
//Schedule hourly
_timer = new Timer(
TriggerHourlyService,
null,
TimeSpan.Zero,
interval
);
}
Task.Run(action);
return Task.CompletedTask;
}
private void TriggerHourlyService(object state)
{
using var scope = _serviceScopeFactory.CreateScope();
IHourlyService hourlyService = scope.ServiceProvider.GetRequiredService<IHourlyService>();
//ASYNC METHOD
hourlyService.ExecuteAll();
}
After some notes from the comments, I've rewritten the code a bit to move away from the tutorial (it seems it was promoting some dangerous practices). However with this rewrite, my application hangs on startup. I believe it's the await Task.Delay(...)
causing the issue, but I was under the impression this was non-blocking.
public async Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan interval = TimeSpan.FromHours(1);
var nextRunTime = DateTime.UtcNow.Date.AddHours(DateTime.UtcNow.Hour + 1);
var curTime = DateTime.UtcNow;
var firstInterval = nextRunTime.Subtract(curTime);
//Run once on startup
await TriggerHourlyService(null);
//First run on next hour
await Task.Delay(firstInterval);
await TriggerHourlyService(null);
//Schedule hourly
_timer = new Timer(
async (state) => await TriggerHourlyService(state),
null,
TimeSpan.Zero,
interval
);
}
private async Task TriggerHourlyService(object state)
{
using var scope = _serviceScopeFactory.CreateScope();
IHourlyService hourlyService = scope.ServiceProvider.GetRequiredService<IHourlyService>();
await hourlyService.ExecuteAll();
}
IHostedServce
is great if you want to run code while your host program is starting and stopping. Each service you have defined will be started and stopped in the order you define them. But if your start method never completes, the host application will never start.
If all you want is a long running loop, BackgroundService
takes care of the start / stop lifetime for you.
public class MyService : BackgroundService{
protected override async Task ExecuteAsync(CancellationToken stoppingToken){
while(!stoppingToken.IsCancellationRequested){
await Task.Delay(...., stoppingToken);
//
}
}
}
Note that your service can still block the startup of the host if you never await
an incomplete task.