问题
I am creating a .net core BackgroundService console application. The default template provides a Worker class that has ExecuteAsync() method, which is great.
My plan is to use a Windows Task Scheduler to schedule to execute the console app once a day. However, I realized the ExecuteAsync() is a continuously executing method. So my question is -- how do I execute the codes in ExecuteAsync() only once and then terminate the Worker? I might have other processes still running, so each individual process needs to run once and terminate itself.
Or is BackgroundService not the best choice for me?
回答1:
I'm afraid you're mistaken:
The ExecuteAsync(CancellationToken) of a registered BackgroundService is only executed once by the .NET Generic Host you are using through the dotnet new worker
template.
The abstract class BackgroundService is a "base class for implementing a long running IHostedService".
It calls your overridden ExecuteAsync(CancellationToken) implementation and yields control to the Host
when an await
operator is encountered.
This enables the StartAsync(CancellationToken) of another IHostedService
to be called during startup before your BackgroundService.ExecuteAsync(CancellationToken)
ran to completion.
Likewise, this also enables StopAsync(CancellationToken) of another IHostedService
to be called during shutdown before your BackgroundService.ExecuteAsync(CancellationToken)
is cancelled gracefully.
Be aware of the "repeat-until-your-BackgroundService-is-stopped"-loop in the Worker Service
-Template
while (!stoppingToken.IsCancellationRequested)
{...}
which represents a long running service.
If your scheduled work is the only IHostedService
in your app and/or it isn't actually a long running service - which it doesn't seem to be when executed on a daily basis - rather than deriving from BackgroundService
, you may just implement IHostedService
directly, add your logic to StartAsync
, and register it via AddHostedService.
When your work is done, you want to shutdown your application host via IHostApplicationLifetime.StopApplication().
To do so, you can access IHostApplicationLifetime
via Constructor Injection, and then call StopApplication()
after your daily operation at the end of StartAsync
.
The HostBuilder which is created by Host.CreateDefaultBuilder already adds a Singleton ApplicationLifetime
.
回答2:
The answer from FlashOver is correct -- ExecuteAsync
is called once. Not repeatedly.
If you want to have something happen at a specific time in a IHostedService
, read this article from Microsoft.
I will paste the code here in case the link rots away:
public class TimedHostedService : IHostedService, IDisposable
{
private int executionCount = 0;
private readonly ILogger<TimedHostedService> _logger;
private Timer _timer;
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service running.");
_timer = new Timer(DoWork, null, TimeSpan.Zero,
TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private void DoWork(object state)
{
var count = Interlocked.Increment(ref executionCount);
_logger.LogInformation(
"Timed Hosted Service is working. Count: {Count}", count);
}
public Task StopAsync(CancellationToken stoppingToken)
{
_logger.LogInformation("Timed Hosted Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public void Dispose()
{
_timer?.Dispose();
}
}
Inject it as so:
services.AddHostedService<TimedHostedService>();
Now, keep in mind that if your application scales horizontally where you have more than one instance then you will have more than one timer running. This generally is not a good thing.
In cases where I need a task to run at a specific time of day I use an Azure Function set up as a CRON job.
来源:https://stackoverflow.com/questions/63302079/net-core-backgroundservice-run-only-once