#c# #asp.net-core #.net-core #asp.net-core-hosted-services
#c# #asp.net-core #.net-core #asp.net-core-hosted-services
Вопрос:
мне нужно запускать размещенную службу в определенное время, например. каждый день в 13:00 часов. Мой типичный метод ExecuteAsync выглядит так:
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
var interval = _configuration.IntervalInSeconds * 1000;
while (!stoppingToken.IsCancellationRequested)
{
// some job
await Task
.Delay(interval, stoppingToken);
}
}
При запуске приложения вызывается метод ExecuteAsync, а затем, например, каждую минуту выполняется какое-либо действие. Теперь я должен выполнить такое действие только в определенный час. Есть ли какой-либо способ добиться такого поведения?? Есть ли какое-либо решение моей проблемы, кроме грубого вычисления следующего времени запуска?? Спасибо за любую помощь.
Комментарии:
1. Вероятно, для этого вам следует использовать библиотеку, такую как Hangfire или Coravle. Таймеры (задача. Задержка также использует таймер) хороши для повторения событий, но не для расписаний. Что произойдет, если ваша служба по какой-либо причине перезапустится? Таймер перезапустится
2. Предлагаемый подход больше похож на: Hangfire, Quartz. NET или, может быть, планировщик Windows
3. Вы могли бы вычислить разницу между следующим запланированным временем и сейчас и дождаться этого момента. Или вы могли бы создать таймер напрямую с начальной задержкой, равной тому времени, которое повторяется каждые 1 день, например
_timer=new Timerr(DoWork,null,ScheduledTime-DateTime.Now,TimeSpan.FromDays(1))
. Но что произойдет, если служба выйдет из строя?4. @404 это даст вам следующее время выполнения, но не будет повторяться каждые 1 день. Что легко сделать, если таймер используется явно вместо
Task.Delay()
. Реальная проблема в том, что 1 день — это слишком долго для цикла таймера5. Вы могли бы просто добавить
if
оператор вокруг// some job
. Быстро и грязно.
Ответ №1:
Используя метод Timer, вы можете запустить задачу в определенное время, вычислить промежуток времени между текущим и целевым временем и передать его в качестве параметра dueTime.
код, как показано ниже:
public Task StartAsync(CancellationToken cancellationToken)
{
//DateTime.Today: 00:00:00
TimeSpan delayTime = DateTime.Today.AddHours(21) - DateTime.Now;
TimeSpan intervalTime = TimeSpan.FromMinutes(1); //86400000s == 1 day
_timer = new Timer(DoWork, null, delayTime, intervalTime);
return Task.CompletedTask;
}
Кроме того, таймер или задача.Методы Delay() в большей степени применяются для выполнения метода с заданными интервалами, если вы хотите реализовать запланированные задачи, я предлагаю вам также попробовать использовать пакет Cronos и выражения Cron для настройки запланированной задачи (ссылка: ссылка).
Пакет Cronos представляет собой легкую, но полноценную библиотеку для анализа выражений cron и вычисления следующих вхождений с учетом часовых поясов и перехода на летнее время. Cronos — это проект с открытым исходным кодом, спонсируемый HangfireIO, и вы можете прочитать подробную документацию в его репозитории GitHub. Подробные инструкции приведены ниже:
-
Установите пакет Cronos через NuGet.
-
Создайте службу CronJobService со следующим кодом:
public abstract class CronJobService : IHostedService, IDisposable { private System.Timers.Timer _timer; private readonly CronExpression _expression; private readonly TimeZoneInfo _timeZoneInfo; protected CronJobService(string cronExpression, TimeZoneInfo timeZoneInfo) { _expression = CronExpression.Parse(cronExpression); _timeZoneInfo = timeZoneInfo; } public virtual async Task StartAsync(CancellationToken cancellationToken) { await ScheduleJob(cancellationToken); } protected virtual async Task ScheduleJob(CancellationToken cancellationToken) { var next = _expression.GetNextOccurrence(DateTimeOffset.Now, _timeZoneInfo); if (next.HasValue) { var delay = next.Value - DateTimeOffset.Now; if (delay.TotalMilliseconds <= 0) // prevent non-positive values from being passed into Timer { await ScheduleJob(cancellationToken); } _timer = new System.Timers.Timer(delay.TotalMilliseconds); _timer.Elapsed = async (sender, args) => { _timer.Dispose(); // reset and dispose timer _timer = null; if (!cancellationToken.IsCancellationRequested) { await DoWork(cancellationToken); } if (!cancellationToken.IsCancellationRequested) { await ScheduleJob(cancellationToken); // reschedule next } }; _timer.Start(); } await Task.CompletedTask; } public virtual async Task DoWork(CancellationToken cancellationToken) { await Task.Delay(5000, cancellationToken); // do the work } public virtual async Task StopAsync(CancellationToken cancellationToken) { _timer?.Stop(); await Task.CompletedTask; } public virtual void Dispose() { _timer?.Dispose(); } } public interface IScheduleConfig<T> { string CronExpression { get; set; } TimeZoneInfo TimeZoneInfo { get; set; } } public class ScheduleConfig<T> : IScheduleConfig<T> { public string CronExpression { get; set; } public TimeZoneInfo TimeZoneInfo { get; set; } } public static class ScheduledServiceExtensions { public static IServiceCollection AddCronJob<T>(this IServiceCollection services, Action<IScheduleConfig<T>> options) where T : CronJobService { if (options == null) { throw new ArgumentNullException(nameof(options), @"Please provide Schedule Configurations."); } var config = new ScheduleConfig<T>(); options.Invoke(config); if (string.IsNullOrWhiteSpace(config.CronExpression)) { throw new ArgumentNullException(nameof(ScheduleConfig<T>.CronExpression), @"Empty Cron Expression is not allowed."); } services.AddSingleton<IScheduleConfig<T>>(config); services.AddHostedService<T>(); return services; } }
-
создайте ScheduleJob.cs:
public class ScheduleJob: CronJobService { private readonly ILogger<ScheduleJob> _logger; public ScheduleJob(IScheduleConfig<ScheduleJob> config, ILogger<ScheduleJob> logger) : base(config.CronExpression, config.TimeZoneInfo) { _logger = logger; } public override Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("ScheduleJob starts."); return base.StartAsync(cancellationToken); } public override Task DoWork(CancellationToken cancellationToken) { _logger.LogInformation($"{DateTime.Now:hh:mm:ss} ScheduleJob is working."); return Task.CompletedTask; } public override Task StopAsync(CancellationToken cancellationToken) { _logger.LogInformation("ScheduleJob is stopping."); return base.StopAsync(cancellationToken); } }
-
Зарегистрируйте службу ScheduleJob в методе ConfigureServices.
public void ConfigureServices(IServiceCollection services) { services.AddHostedService<HelloWorldHostedService>(); services.AddCronJob<ScheduleJob>(c=> { c.TimeZoneInfo = TimeZoneInfo.Local; c.CronExpression = @"25 21 * * *"; // 21:25 PM daily. }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); }
Затем результат, как показано ниже:
Комментарии:
1. Как настроить выполнение запланированного задания только один раз? Например, выполнение задания запланировано на 5 минут. После выполнения задания оно должно остановиться и больше не выполняться.
2. В строке
if (delay.TotalMilliseconds <= 0)
нужно ли возвращать в этом блоке if? В противном случае он будет напоминать о себе, пока не примет положительное значение, но отрицательное значение с первой итерации в конечном итоге будет установлено, когда оно развернется и выдаст исключение. (кроме этого, он отлично работает).