如何在一天中的特定时间 运行 .net Core IHosted 服务?

How to run .net Core IHosted Service at specific time Of the day?

我在 .Net 核心 Ihosted 服务中有以下定时器 运行,

TimeSpan ScheduledTimespan;
string[] formats = { @"hh\:mm\:ss", "hh\:mm" };
string strTime = Startup.Configuration["AppSettings:JobStartTime"].ToString();
var success = TimeSpan.TryParseExact(strTime, formats, CultureInfo.InvariantCulture, out ScheduledTimespan);
Timer _timer = new Timer(JobToRun, null, TimeSpan.Zero, ScheduledTimespan);

我正在使用这个特定的重载,

public Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period);

但是 JobToRun 在控件到达它时立即执行。 如何在每天的特定时间达到 运行? 提前致谢。

以下函数returns解析作业运行时间

private static TimeSpan getScheduledParsedTime()
{
     string[] formats = { @"hh\:mm\:ss", "hh\:mm" };
     string jobStartTime = "07:10";
     TimeSpan.TryParseExact(jobStartTime, formats, CultureInfo.InvariantCulture, out TimeSpan ScheduledTimespan);
     return ScheduledTimespan;
}

跟随函数returns 从当天当前时间开始的延迟时间。 如果一天中的当前时间超过作业 运行 时间,适当的延迟将被添加到作业 运行 中,如下所示,

private static TimeSpan getJobRunDelay()
{
    TimeSpan scheduledParsedTime = getScheduledParsedTime();
    TimeSpan curentTimeOftheDay = TimeSpan.Parse(DateTime.Now.TimeOfDay.ToString("hh\:mm"));
    TimeSpan delayTime = scheduledParsedTime >= curentTimeOftheDay
        ? scheduledParsedTime - curentTimeOftheDay
        : new TimeSpan(24, 0, 0) - curentTimeOftheDay + scheduledParsedTime;
    return delayTime;
}

下面的计时器会根据JobRunDelay每天

调用methodToExecute
_timer = new Timer(methodToExecute, null, getJobRunDelay(), new TimeSpan(24, 0, 0));

使用 Cron 作业

  1. 创建一个 ASP.NET 核心 Web 项目

  2. 添加 NuGet 包Microsoft.Extensions.Hosting

  3. 添加 NuGet 包 Cronos

  4. Crete Abstract class CronJobService - 在这个 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();
      }
    }
    
  5. 继承自 CronJobService

      public class XXXCronJob:CronJobService
      {
        private readonly ILogger<CronJob> _logger;
    
        public XXXCronJob(IScheduleConfig<CronJob > config, ILogger<CronJob> logger)
        : base(config.CronExpression, config.TimeZoneInfo)
        {
         _logger = logger;
        }
    
        public override Task StartAsync(CancellationToken cancellationToken)
        {
         _logger.LogInformation("CronJob  starts.");
         return base.StartAsync(cancellationToken);
        }
    
        public override Task DoWork(CancellationToken cancellationToken)
        {
         _logger.LogInformation($"{DateTime.Now:hh:mm:ss} CronJob  is working.");
         return Task.CompletedTask;
        }
    
        public override Task StopAsync(CancellationToken cancellationToken)
        {
         _logger.LogInformation("CronJob  is stopping.");
         return base.StopAsync(cancellationToken);
        }
     }
    
  6. 添加新接口 IScheduleConfig

     public interface IScheduleConfig<T>
     {
       string CronExpression { get; set; }
       TimeZoneInfo TimeZoneInfo { get; set; }
     }
    
  7. 实施 IScheduleConfig

     public class ScheduleConfig<T> : IScheduleConfig<T>
     {
       public string CronExpression { get; set; }
       public TimeZoneInfo TimeZoneInfo { get; set; }
     }
    
  8. 添加扩展class ScheduledServiceExtensions

     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;
        }
     }
    
  9. 使用泛型安排多个 Cron 作业

     public void ConfigureServices(IServiceCollection services)
     {
       services.AddCronJob<XXXCronJob>(c =>
       {
         c.TimeZoneInfo = TimeZoneInfo.Local;
         c.CronExpression = @"00 05 * * *";
       });
       services.AddCronJob<YYYCronJob>(c =>
       {
         c.TimeZoneInfo = TimeZoneInfo.Local;
         c.CronExpression = @"00 08 * * *";
       });
       services.AddCronJob<ZZZCronJob>(c =>
       {
         c.TimeZoneInfo = TimeZoneInfo.Local;
         c.CronExpression = @"30 14 * * *";
       });
     }
    

    第一个是 05:00 上午。

    第二个是 08:00 AM

    最后一个是 14:30 下午。