三、实现方式(二)
在 ASP.NET Core 2.1中, 提供了一个名为 BackgroundService 的类,它在 Microsoft.Extensions.Hosting 命名空间中,查看一下它的源码:
1. 1 using System; 2 using System.Threading; 3 using System.Threading.Tasks; 4 5 namespace Microsoft.Extensions.Hosting 6 { 7 /// <summary> 8 /// Base class for implementing a long running <see cref="IHostedService"/>. 9 /// </summary> 10 public abstract class BackgroundService : IHostedService, IDisposable 11 { 12 private Task _executingTask; 13 private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource(); 14 15 /// <summary> 16 /// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task that represents 17 /// the lifetime of the long running operation(s) being performed. 18 /// </summary> 19 /// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param> 20 /// <returns>A <see cref="Task"/> that represents the long running operations.</returns> 21 protected abstract Task ExecuteAsync(CancellationToken stoppingToken); 22 23 /// <summary> 24 /// Triggered when the application host is ready to start the service. 25 /// </summary> 26 /// <param name="cancellationToken">Indicates that the start process has been aborted.</param> 27 public virtual Task StartAsync(CancellationToken cancellationToken) 28 { 29 // Store the task we're executing 30 _executingTask = ExecuteAsync(_stoppingCts.Token); 31 32 // If the task is completed then return it, this will bubble cancellation and failure to the caller 33 if (_executingTask.IsCompleted) 34 { 35 return _executingTask; 36 } 37 38 // Otherwise it's running 39 return Task.CompletedTask; 40 } 41 42 /// <summary> 43 /// Triggered when the application host is performing a graceful shutdown. 44 /// </summary> 45 /// <param name="cancellationToken">Indicates that the shutdown process should no longer be graceful.</param> 46 public virtual async Task StopAsync(CancellationToken cancellationToken) 47 { 48 // Stop called without start 49 if (_executingTask == null) 50 { 51 return; 52 } 53 54 try 55 { 56 // Signal cancellation to the executing method 57 _stoppingCts.Cancel(); 58 } 59 finally 60 { 61 // Wait until the task completes or the stop token triggers 62 await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken)); 63 } 64 } 65 66 public virtual void Dispose() 67 { 68 _stoppingCts.Cancel(); 69 } 70 } 71 }
可以看出它一样是继承自 IHostedService, IDisposable , 它相当于是帮我们写好了一些“通用”的逻辑, 而我们只需要继承并实现它的 ExecuteAsync 即可。
也就是说,我们只需在这个方法内写下这个服务需要做的事,这样上面的刷新Token的Service就可以改写成这样:
1 internal class TokenRefreshService : BackgroundService 2 { 3 private readonly ILogger _logger; 4 5 public TokenRefreshService(ILogger<TokenRefresh2Service> logger) 6 { 7 _logger = logger; 8 } 9 10 protected override async Task ExecuteAsync(CancellationToken stoppingToken) 11 { 12 _logger.LogInformation("Service starting"); 13 14 while (!stoppingToken.IsCancellationRequested) 15 { 16 _logger.LogInformation(DateTime.Now.ToLongTimeString() + ": Refresh Token!");//在此写需要执行的任务 17 await Task.Delay(5000, stoppingToken); 18 } 19 20 _logger.LogInformation("Service stopping"); 21 } 22 }
是不是简单了不少。(同样这里为了方便测试写了5秒执行一次)
四. 注意事项
感谢@ 咿呀咿呀哟在评论中的提醒,当项目部署在IIS上的时候, 当应用程序池回收的时候,这样的后台任务也会停止执行。
经测试:
1. 当IIS上部署的项目启动后,后台任务随之启动,任务执行相应的log正常输出。
2. 手动回收对应的应用程序池,任务执行相应的log输出停止。
3. 重新请求该网站,后台任务随之启动,任务执行相应的log重新开始输出。
所以不建议在这样的后台任务中做一些需要固定定时执行的业务处理类的操作,但对于缓存刷新类的操作还是可以的,因为当应用程序池回收后再次运行的时候,后台任务会随着启动。