在.NET 6中引入了新Timer:System.Threading.PeriodicTimer,它和之前的Timer相比,最大的区别就是新的PeriodicTimer事件处理可以方便地使用异步,消除使用callback机制减少使用复杂度。讲解PeriodicTimer之前我们先来看以下该怎么使用它:
using var cts = new CancellationTokenSource(); Console.CancelKeyPress += (sender, e) => { e.Cancel = true; cts.Cancel(); }; using var timer = new PeriodicTimer(TimeSpan.FromSeconds(3)); try { while (await timer.WaitForNextTickAsync(cts.Token)) { Console.WriteLine($"触发事件({DateTime.Now:HH:mm:ss})"); } } catch (OperationCanceledException e) { Console.WriteLine("出现异常,运行停止"+e.Message); }
一般来说PeriodicTimer会结合CancellationToken来使用,需要注意的是当CancellationToken被取消时会抛出OperationCanceledException异常,因此需要手动处理异常。另外当PeriodicTimer被Dispose掉时,这个timer就失效且无法恢复,我们来看下面这个示例:
var timer1 = new PeriodicTimer(TimeSpan.FromSeconds(10)); timer1.Dispose(); if (await timer1.WaitForNextTickAsync()) { Console.WriteLine("事件执行"); }
上面的代码中在WaitForNextTickAsync之前就已经调用了Dispose(),这时WaitForNextTickAsync方法就只能返回false,因此后续的代码将不会被执行。在以前的.NET版本中我们会使用Timer来做后台任务,那么在.NET6中我们同样可以这么做,官方示例代码如下:
public abstract class TimerScheduledService : BackgroundService { private readonly PeriodicTimer _timer; private readonly TimeSpan _period; protected readonly ILogger Logger; protected TimerScheduledService(TimeSpan period, ILogger logger) { Logger = logger; _period = period; _timer = new PeriodicTimer(_period); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { try { while (await _timer.WaitForNextTickAsync(stoppingToken)) { try { Logger.LogInformation("Begin execute service"); await ExecuteInternal(stoppingToken); } catch (Exception ex) { Logger.LogError(ex, "Execute exception"); } finally { Logger.LogInformation("Execute finished"); } } } catch (OperationCanceledException operationCancelledException) { Logger.LogWarning(operationCancelledException, "service stopped"); } } protected abstract Task ExecuteInternal(CancellationToken stoppingToken); public override Task StopAsync(CancellationToken cancellationToken) { Logger.LogInformation("Service is stopping."); _timer.Dispose(); return base.StopAsync(cancellationToken); } } public class TimedHealthCheckService : TimerScheduledService { public TimedHealthCheckService(ILogger<TimedHealthCheckService> logger) : base(TimeSpan.FromSeconds(5), logger) { } protected override Task ExecuteInternal(CancellationToken stoppingToken) { Logger.LogInformation("Executing..."); return Task.CompletedTask; } }
总结
PeriodicTimer相比之前的Timer来说,有下面几个特点:
- 没有callback来绑定事件;
- 不会发生重入,只允许有一个消费者,不允许同一个PeriodicTimer在不同的地方同时WaitForNextTickAsync,不需要自己做排他锁来实现不能重入;
- 异步化。之前的timer的callback都是同步的,使用新timer可以使用异步方法,避免了编写Sync over Async代码;
- Dispose之后,实例就无法使用,并且WaitForNextTickAsync始终返回false。