ASP.NET Core 2.1 : 十一. 如何在後臺執行一個任務

FlyLolo發表於2018-06-27

  在大部分程式中一般都會需要用到後臺任務, 比如定時更新快取或更新某些狀態。(ASP.NET Core 系列目錄

一、應用場景

  以呼叫微信公眾號的Api為例, 經常會用到access_token,官方文件這樣描述:“是公眾號的全域性唯一介面呼叫憑據,有效期目前為2個小時,需定時重新整理,重複獲取將導致上次獲取的access_token失效,建議公眾號開發者使用中控伺服器統一獲取和重新整理Access_token,其他業務邏輯伺服器所使用的access_token均來自於該中控伺服器,不應該各自去重新整理,否則容易造成衝突,導致access_token覆蓋而影響業務。”

  在這個場景中我們可以建立一個後臺執行的服務,按照access_token的有效期定時執行去請求獲取新的access_token並儲存,其他所有需要用到這個access_token的都到這個共有的access_token。

二、實現方式(一)

  ASP.NET Core 在2.0的時候就提供了一個名為IHostedService的介面,我們要做的只有兩件事:

    1. 實現它。

    2. 將這個介面實現註冊到依賴注入服務中。

  A. 實現IHostedService的介面

            首先看一下這個IHostedService:

    public interface IHostedService
    {
        Task StartAsync(CancellationToken cancellationToken);
        Task StopAsync(CancellationToken cancellationToken);
    }

通過名字就可以看出來,一個是這個服務啟動的時候做的操作,另一個則是停止的時候。

新建一個類 TokenRefreshService  實現 IHostedService ,如下面程式碼所示:

 1     internal class TokenRefreshService : IHostedService, IDisposable
 2     {
 3         private readonly ILogger _logger;
 4         private Timer _timer;
 5 
 6         public TokenRefreshService(ILogger<TokenRefreshService> logger)
 7         {
 8             _logger = logger;
 9         }
10 
11         public Task StartAsync(CancellationToken cancellationToken)
12         {
13             _logger.LogInformation("Service starting");
14             _timer = new Timer(Refresh, null, TimeSpan.Zero,TimeSpan.FromSeconds(5));
15             return Task.CompletedTask;
16         }
17 
18         private void Refresh(object state)
19         {
20             _logger.LogInformation(DateTime.Now.ToLongTimeString() + ": Refresh Token!"); //在此寫需要執行的任務
21             
22         }
23 
24         public Task StopAsync(CancellationToken cancellationToken)
25         {
26             _logger.LogInformation("Service stopping");
27             _timer?.Change(Timeout.Infinite, 0);
28             return Task.CompletedTask;
29         }
30 
31         public void Dispose()
32         {
33             _timer?.Dispose();
34         }
35     }

既然是定時重新整理任務,那麼就用了一個timer, 當服務啟動的時候啟動它,由它定時執行Refresh方法來獲取新的Token。

這裡為了方便測試寫了5秒執行一次, 實際應用還是讀取配置檔案比較好, 結果如下:

BackService.TokenRefreshService:Information: 17:23:30: Refresh Token!
BackService.TokenRefreshService:Information: 17:23:35: Refresh Token!
BackService.TokenRefreshService:Information: 17:23:40: Refresh Token!
BackService.TokenRefreshService:Information: 17:23:45: Refresh Token!
BackService.TokenRefreshService:Information: 17:23:50: Refresh Token!

 B. 在依賴注入中註冊這個服務。

在Startup的ConfigureServices中註冊這個服務,如下程式碼所示:

services.AddSingleton<IHostedService, TokenRefreshService>();

 

三、實現方式(二)

 在 ASP.NET Core 2.1中, 提供了一個名為 BackgroundService  的類,它在 Microsoft.Extensions.Hosting 名稱空間中,檢視一下它的原始碼:

 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重新開始輸出。

所以不建議在這樣的後臺任務中做一些需要固定定時執行的業務處理類的操作,但對於快取重新整理類的操作還是可以的,因為當應用程式池回收後再次執行的時候,後臺任務會隨著啟動。

 

 

github地址

相關文章