首页 > 解决方案 > ASP.NET Core 3.1 托管服务针对给定计划运行多次(它应该每天运行一次)

问题描述

我在后台运行的 asp.net core 3.1 应用程序中有这个托管服务作业。在开发中一切正常,但是当我将它部署到生产中时,从这个抽象类继承的任何作业都将多次运行 ExecuteOnStartAsync() 方法。在开发中测试 DummyTestJob 类时。它运行得非常好,每 10 秒调用一次。但是,当我将其部署到生产环境中时,该作业应该在凌晨 2:00 发送 1 封电子邮件,早上我将收到 5-13 封电子邮件,而且这个数字也是任意的。这部署在 AWS EC2 Windows 机器上,我不确定它是否与 cpu 核心数量等有关,但是我的本地开发工作站有 8 个核心,它仍然运行一次。所以我不确定还有什么事情发生。

public abstract class ScheduledBackgroundWorker<T> : BaseBackgroundWorker<T> where T : class
{
    private CrontabSchedule schedule;
    private DateTime nextRun;
    protected string JobName => this.GetType().Name;
    protected abstract string JobDescription { get; }
    protected abstract string Schedule { get; }

    protected ScheduledBackgroundWorker(IServiceProvider services,
       ILogger<T> logger) : base(services, logger)
    {
        schedule = CrontabSchedule.Parse(Schedule, new CrontabSchedule.ParseOptions
        {
            IncludingSeconds = true
        });
        nextRun = schedule.GetNextOccurrence(DateTime.Now);
    }

    protected abstract Task ExecuteOnStartAsync(CancellationToken cancellationToken);
    protected abstract Task ExecuteOnStopAsync(CancellationToken cancellationToken);

    protected override async Task ExecuteStartAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("{jobName} Schedule {schedule} was scheduled and next run will be at {nextRun} - {now}", JobName, Schedule, nextRun.ToString("MM/dd/yyyy hh:mm:ss t"), DateTime.Now);
        using var scope = services.CreateScope();
        var cache =
            scope.ServiceProvider
                .GetRequiredService<IDistributedCache>();
        do
        {
            var hasRun = await cache.GetAsync(JobName);
            if (hasRun == null)
            {
                var now = DateTime.Now;
                if (now > nextRun)
                {
                    logger.LogInformation("{jobName} Schedule {schedule} is running - {now}", JobName, Schedule, DateTime.Now);
                    await ExecuteOnStartAsync(cancellationToken);
                    nextRun = schedule.GetNextOccurrence(DateTime.Now);

                    var currentTime = nextRun.ToString();
                    byte[] encodedCurrentTime = Encoding.UTF8.GetBytes(currentTime);
                    var options = new DistributedCacheEntryOptions().SetAbsoluteExpiration(TimeSpan.FromSeconds(60));
                    await cache.SetAsync(JobName, encodedCurrentTime, options, cancellationToken);

                    logger.LogInformation("{jobName} Schedule {schedule} has finished and next run will be at {nextRun} - {now}", JobName, Schedule, nextRun.ToString("MM/dd/yyyy hh:mm:ss t"), DateTime.Now);
                }
            }
            await Task.Delay(5000, cancellationToken); //5 seconds delay
        }
        while (!cancellationToken.IsCancellationRequested);

        await Task.CompletedTask;
    }

    protected override async Task ExecuteStopAsync(CancellationToken cancellationToken)
    {
        await ExecuteOnStopAsync(cancellationToken);
        logger.LogInformation("{jobName} Schedule {schedule} was stopped - {now}", JobName, Schedule, DateTime.Now);
        await Task.CompletedTask;
    }

    public override void Dispose()
    {
    }
}

即使将运行状态保存在缓存中,它也会做与没有缓存相同的事情。我正在使用这个名为 NCrontab 的库来设置 cron 计划。不确定它是否与它有关。但它再次在开发中工作,在生产中失败。

public class DummyTestJob : ScheduledBackgroundWorker<DummyTestJob>
{
public DummyTestJob(IServiceProvider services,
    ILogger<DummyTestJob> logger) : base(services, logger)
{

}

protected override string JobDescription => "Dummy Test";

protected override string Schedule => "10 * * * * *";

protected override async Task ExecuteOnStartAsync(CancellationToken cancellationToken)
{
    using var scope = services.CreateScope();
    var apiClient =
        scope.ServiceProvider
            .GetRequiredService<IApiClient>();

    var runtimeEnvironment = scope.ServiceProvider.GetRequiredService<IRuntimeEnvironment>();

    try
    {
        var page = 1;
        var size = 100;
        var loop = true;
        List<User> users = new List<User>();
        do
        {
            try
            {
                var response = await apiClient.GetAsync($"users?page={page}&size={size}", null, null);

                if (response.Count > 0)
                {
                    users.AddRange(response.Data);
                    page++;
                }
                else
                {
                    loop = false;
                }
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Failure getting users for page {page}", page);
                loop = false;
            }

        } while (loop);

        List<string> userNames = new List<string>();

        foreach (var user in Users)
        {
            if (user.IsActive == 1)
            {
                userNames.Add(user.Name);
            }
        }

        users.Clear();
        users = null;

        foreach (var userName in userNames)
        {
            System.Console.WriteLine($"Hello {userName}...\n");
        }
    }
    catch (OperationCanceledException ex)
    {
        logger.LogError(ex, "{JobName} execution canceled - {now}", JobName, DateTime.Now);
    }

    await Task.CompletedTask;
}

protected override async Task ExecuteOnStopAsync(CancellationToken cancellationToken)
{
    await Task.CompletedTask;
}
}


public abstract class BaseBackgroundWorker<T> : BackgroundService where T : class
{
    protected readonly IServiceProvider services;
    protected readonly ILogger<T> logger;

    // inject a logger
    protected BaseBackgroundWorker(IServiceProvider services,
        ILogger<T> logger)
    {
        this.services = services;
        this.logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken cancellationToken)
    {
        await ExecuteStartAsync(cancellationToken);
        await Task.CompletedTask;
    }

    protected abstract Task ExecuteStartAsync(CancellationToken cancellationToken);
    protected abstract Task ExecuteStopAsync(CancellationToken cancellationToken);

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        await ExecuteStopAsync(cancellationToken);
        await base.StopAsync(cancellationToken);
        await Task.CompletedTask;
    }
}

标签: c#asp.net-core-3.1

解决方案


这是因为在 IIS 中,我将 API 的 Max Worker Process 设置为 5。将其重置回 1 解决了问题


推荐阅读