簡體   English   中英

手動觸發 IHostedService 計划任務

[英]Manually triggering IHostedService scheduled tasks

我有一個 .NET 核心 API ,其中包含重復的 email 發件人計划任務。 我找到了各種實現它的方法並使用了其中一種。 它按預期每分鍾都在工作,但我不知道這是否是正確的創建方式。

我將它存儲在數據庫表中,例如 (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;
    }
}

ScheduledProcessor.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);
}

背景服務.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以手動執行在命令行上指定的任務,如果它作為控制台應用程序運行(而不是服務)。 有點乏味,但可行。

旁注:我發現每個服務生命周期的范圍令人驚訝; 它使所有作用域依賴項成為單例。 我發現不那么令人驚訝的是每次出現的范圍。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM