Dmi*_*nov 4 c# asynchronous timer task asp.net-core-hosted-services
我有一个要求,后台服务应该在Process每天凌晨 0:00 运行方法
因此,我的一位团队成员编写了以下代码:
public class MyBackgroundService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
public MyBackgroundService(ILogger<MyBackgroundService> logger)
{
_logger = logger;
}
public void Dispose()
{
_timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan interval = TimeSpan.FromHours(24);
TimeSpan firstCall = DateTime.Today.AddDays(1).AddTicks(-1).Subtract(DateTime.Now);
Action action = () =>
{
Task.Delay(firstCall).Wait();
Process();
_timer = new Timer(
ob => Process(),
null,
TimeSpan.Zero,
interval
);
};
Task.Run(action);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
private Task Process()
{
try
{
// perform some database operations
}
catch (Exception e)
{
_logger.LogError(e, e.Message);
}
return Task.CompletedTask;
}
}
Run Code Online (Sandbox Code Playgroud)
这段代码按预期工作。但我不喜欢它同步等待直到Process第一次调用,因此线程被阻塞并且不执行任何有用的工作(如果我错了,请纠正我)。
我可以在其中创建一个异步操作并等待,如下所示:
public Task StartAsync(CancellationToken cancellationToken)
{
// code omitted for brevity
Action action = async () =>
{
await Task.Delay(firstCall);
await Process();
// code omitted for brevity
}
Run Code Online (Sandbox Code Playgroud)
但我不确定Task.Run这里使用是不是一件好事,因为Process方法应该执行一些 I/O 操作(查询数据库并插入一些数据),并且不建议在 ASP.NET 环境中使用Task.Run。
我重构StartAsync如下:
public async Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan interval = TimeSpan.FromHours(24);
TimeSpan firstDelay = DateTime.Today.AddDays(1).AddTicks(-1).Subtract(DateTime.Now);
await Task.Delay(firstDelay);
while (!cancellationToken.IsCancellationRequested)
{
await Process();
await Task.Delay(interval, cancellationToken);
}
}
Run Code Online (Sandbox Code Playgroud)
这允许我根本不使用计时器MyBackgroundService。
我应该使用第一种方法“timer + Task.Run”还是第二种方法“while循环+ Task.Delay”?
循环while方法更简单、更安全。使用该类Timer有两个隐藏的陷阱:
不过,您当前的while循环实现可以通过多种方式进行改进:
DateTime.Now在计算过程中多次读取TimeSpan可能会产生意外结果,因为每次DateTime返回的值可能不同。DateTime.Now最好将 存储DateTime.Now在变量中,并在计算中使用存储的值。cancellationToken.IsCancellationRequested中的条件可能会导致不一致的取消行为。完全跳过此检查更简单且一致。通过这种方式取消令牌总是会产生一个结果。whileTask.DelayOperationCanceledExceptionProcess不应影响下一个操作的调度。一种方法是Task.Delay在开始之前创建任务Process,并await在完成之后创建任务Process。或者您可以根据当前时间重新计算下一个延迟。这还有一个优点,即在系统时间发生变化的情况下,调度将自动调整。这是我的建议:
public async Task StartAsync(CancellationToken cancellationToken)
{
TimeSpan scheduledTime = TimeSpan.FromHours(0); // midnight
TimeSpan minimumIntervalBetweenStarts = TimeSpan.FromHours(12);
while (true)
{
var scheduledDelay = scheduledTime - DateTime.Now.TimeOfDay;
while (scheduledDelay < TimeSpan.Zero)
scheduledDelay += TimeSpan.FromDays(1);
await Task.Delay(scheduledDelay, cancellationToken);
var delayBetweenStarts =
Task.Delay(minimumIntervalBetweenStarts, cancellationToken);
await ProcessAsync();
await delayBetweenStarts;
}
}
Run Code Online (Sandbox Code Playgroud)
这样做的原因minimumIntervalBetweenStarts是为了防止系统时间发生非常剧烈的变化。
| 归档时间: |
|
| 查看次数: |
4798 次 |
| 最近记录: |