c# - 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;
}
}
解决方案
这是因为在 IIS 中,我将 API 的 Max Worker Process 设置为 5。将其重置回 1 解决了问题
推荐阅读
- azure-traffic-manager - 如何将流量管理器与 Azure 存储静态网站一起使用?
- android - 使用 volley 在服务器中上传 csv 文件的问题
- sql-server - 无法使用 ADO 在 MS SQL Express 服务器上开始事务
- bash - 未知速记标志:-d 中的 'd'
- react-day-picker - 每天显示 2 个日历(格鲁吉亚语和回历)
- android - Android WebView 支持 WebAuthn?
- azure - 如何通过终端(脚本)而不是从门户将 api 导入/添加到 azure 中的 apim?
- jquery - 如何使用 JQuery .append() 方法添加包含 html 的字符串?
- hibernate - org.springframework.data.util.Pair 类型的目标 bean 不是持久实体的类型 (com.mycompany.hibernatepoc.ConnectionManager
- javascript - 缩短一个大字符串而不削减单词并在Javascript中保存到数组中