计时器(Timer)调用异步(Async)任务

我最近在 .NET Core 应用中编写托管服务,该服务基于计时器(Timer)在后台运行作业,而该作业为一个异步(async)任务。通常情况下,计时器的委托只能接收同步作业。对于异步任务,需要作出相应的处理,下文叙述如何实现。

首先,我们看一下在通常情况下,计时器必须如下代码所示同步运行作业:

public override async Task StartAsync(CancellationToken cancellationToken) 
{ 
   _logger.LogDebug("服务正在启动"); 
   _timer = new Timer(ExecuteTask, null, Timeout.Infinite, 0); 
   var random = new Random(); 
   _timer.Change(TimeSpan.FromSeconds(random.Next(10)), _period);
} 
/// <summary> 
///  
/// </summary> 
/// <param name="state"></param>
private void ExecuteTask(object state = null) 
{ 
    _logger.LogDebug("启动定时执行服务"); 
   ///To do something
}

如果想在计时器上运行异步,是否有方法可以按时间安排利用异步代码?

异步的全部目的是不阻塞主线程。 但是,本文应用中已经是一个后台线程,所以实际上并不重要,除非它是 ASP.NET Core 应用程序——因为线程池有限,耗尽它意味着无法再处理更多请求。 如果真的想异步运行它,只需让它异步:

private async void ExecuteTask(object state) 
{ 
   //await stuff here 
}

计时器调用是“发后即忘”模式,即在调用ExecuteTask方法时,并不会关心(或检查)它是否仍在运行或是否失败的任何事情,无论它是否运行方法中的异步方法。

我们可以通过将ExecuteTask方法的所有内容包装在try/catch块中,并确保将其在某个地方记录下来,便于了解发生了什么事情来降低故障。另一个问题就是知道它是否仍在运行(同样地,即使没有运行异步,这也是一个问题)。有一种方法可以缓解这种情况:

private Task doWorkTask; 
private void ExecuteTask(object state) 
{ 
   doWorkTask = DoWork(); 
} 
private async Task DoWork() 
{ 
   //await stuff here 
}

在这种情况下,计时器只会启动任务。 但是不同之处在于要保留对任务的引用。 这样就可以在代码中的其他任何位置检查Task的状态。 例如,如果要验证是否已完成,则可以查看doWorkTask.IsCompleteddoWorkTask.Status。除此以外,当应用程序关闭时,可以使用:

await doWorkTask;

确保在关闭应用程序之前任务已完成。 否则,该线程将被杀死,可能会使事物处于不一致状态。同时注意,如果在DoWork()中发生未处理的异常,则使用await doWorkTask将引发异常。

最后,从实际成功实现计时器调用异步方法的应用案例中,附上关键部分代码:

namespace Pzy.Scheduer 
{ 
   public abstract class ScheduedService : BackgroundService 
   { 
       private readonly Timer _timer; 
       private readonly TimeSpan _period; 
       protected readonly ILogger _logger; 
       private Task _executingTimerTask; 
       /// <summary> 
       ///  
       /// </summary> 
       /// <param name="period">定时器周期</param> 
       /// <param name="logger"></param> 
       protected ScheduedService(TimeSpan period, ILogger logger) 
       { 
           _logger = logger; 
           _period = period; 
           _timer = new Timer(ExecuteTask, null, Timeout.Infinite, 0); 
       } 
       private void ExecuteTask(object state = null) 
       { 
           try 
           { 
               _logger.LogDebug("启动定时执行服务"); 
               if (_executingTimerTask == null || _executingTimerTask.IsCompleted) 
               { 
                   _executingTimerTask = TimerExecuteAsync(); 
               } 
           } 
           catch (Exception ex) 
           { 
               _logger.LogError(ex, "定时执行异常"); 
           } 
           finally 
           { 
               _logger.LogDebug("定时执行完成"); 
           } 
       } 
       /// <summary> 
       /// 计时器执行(异步) 
       /// </summary> 
       /// <returns></returns> 
       protected abstract Task TimerExecuteAsync(); 
       public override void Dispose() 
       { 
           _timer?.Dispose(); 
           base.Dispose(); 
       } 
       public override async Task StartAsync(CancellationToken cancellationToken) 
       { 
           _logger.LogDebug("服务正在启动"); 
           var random = new Random(); 
           _timer.Change(TimeSpan.FromSeconds(random.Next(10)), _period); 
           await base.StartAsync(cancellationToken); 
       } 
       public override async Task StopAsync(CancellationToken cancellationToken) 
       { 
           _logger.LogDebug("正在停止服务"); 
           //先执行完定时器任务 
           await _executingTimerTask; 
           _timer?.Change(Timeout.Infinite, 0); 
           await base.StopAsync(cancellationToken); 
       } 
   } 
}

本文部分内容译自:https://stackoverflow.com/questions/53844586/async-timer-in-scheduler-background-service

《计时器(Timer)调用异步(Async)任务》的相关评论

发表评论

必填项已用 * 标记,邮箱地址不会被公开。