手动触发IHostedService计划任务



我有一个.NET Core API,其中包含重复的电子邮件发件人计划任务。我已经找到了实现它的各种方法,并使用了其中一种方法。它每分钟都在按预期工作,但我不知道这是否是正确的创作方式。

我将它存储在数据库表中,如(Id、TaskName、IsActive、LastStartedDate(,以检查它是否处于活动状态。但现在我可以在任务本身中完成,而不是在运行之前。这是我的第一个问题。

第二个问题是,有时由于各种原因,我想手动触发它。我也做不到。这是我的主要问题。我不知道这是否可能。

这是我的代码:

EmailSenderTask.cs:

public class EmailSenderTask : ScheduledProcessor
{
public EmailSenderTask(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory) { }
protected override string Schedule => "*/1 * * * *";
public override Task ProcessInScope(IServiceProvider serviceProvider)
{
var context = serviceProvider.GetRequiredService<DataContext>();
var emailSender = serviceProvider.GetRequiredService<IEmailSender>();
var task = context.ScheduleTask.FirstOrDefault(x => x.SystemName.Equals("EmailSenderTask"));
if (task.Active)
{
// Doing some stuff...
task.LastStartDate = DateTime.Now;
context.SaveChangesAsync();
}
return Task.CompletedTask;
}
}

调度处理器.cs:

public abstract class ScheduledProcessor : ScopedProcessor
{
private CrontabSchedule _schedule;
private DateTime _nextRun;
protected abstract string Schedule { get; }
public ScheduledProcessor(IServiceScopeFactory serviceScopeFactory) : base(serviceScopeFactory)
{
_schedule = CrontabSchedule.Parse(Schedule);
_nextRun = _schedule.GetNextOccurrence(DateTime.Now);
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
do
{
var now = DateTime.Now;
var nextrun = _schedule.GetNextOccurrence(now);
if (now > _nextRun)
{
await Process();
_nextRun = _schedule.GetNextOccurrence(DateTime.Now);
}
await Task.Delay(5000, stoppingToken); //5 seconds delay
}
while (!stoppingToken.IsCancellationRequested);
}
}

ScopedProcessor.ts:

public abstract class ScopedProcessor : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public ScopedProcessor(IServiceScopeFactory serviceScopeFactory) : base()
{
_serviceScopeFactory = serviceScopeFactory;
}
protected override async Task Process()
{
using (var scope = _serviceScopeFactory.CreateScope())
{
await ProcessInScope(scope.ServiceProvider);
}
}
public abstract Task ProcessInScope(IServiceProvider serviceProvider);
}

BackgroundService.cs:

public abstract class BackgroundService : IHostedService
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
public virtual Task StartAsync(CancellationToken cancellationToken)
{
_executingTask = ExecuteAsync(_stoppingCts.Token);
if (_executingTask.IsCompleted)
{
return _executingTask;
}
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
if (_executingTask == null)
{
return;
}
try
{
_stoppingCts.Cancel();
}
finally
{
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
protected virtual async Task ExecuteAsync(CancellationToken stoppingToken)
{
do
{
await Process();
await Task.Delay(5000, stoppingToken); //5 seconds delay
}
while (!stoppingToken.IsCancellationRequested);
}
protected abstract Task Process();
}

启动.cs:

services.AddSingleton<IHostedService, EmailSenderTask>();

如何检查任务是否为活动

我将它存储在数据库表中,如(Id、TaskName、IsActive、LastStartedDate(,以检查它是否处于活动状态。但现在我可以在任务本身中完成,而不是在运行之前。这是我的第一个问题。

您要查找的是一个";租赁";。虽然在数据库中存储租约是可能的,但这也不是很容易。使用具有到期后删除语义的数据存储要容易得多,比如CosmosDb或Redis。

然后,当后台服务触发(或手动触发(时,它会尝试获取租约,如果租约已经被占用,它会跳过对该事件的处理。

第二个问题是,有时由于各种原因,我想手动触发它。我也做不到。这是我的主要问题。我不知道这是否可能。

.NET中没有任何内置功能。我所做的是为我想要手动执行的周期性后台任务添加一个单独的接口,然后更新Program.cs以手动执行命令行上指定的任务(如果它作为控制台应用程序(而不是服务(运行(。有点乏味,但可行。

旁注:我发现每个使用寿命的范围令人惊讶;它使所有作用域的依赖关系成为单例。我发现不那么令人惊讶的是每次发生的范围。

最新更新