背景常駐執行計時工作的實作方式有很多種,而我習慣在 ASP.NET Coe Web Application 使用 BackgroundService 然後搭配 Task.Delay 的方式來完成計時執行工作的處理,而在 .NET 6 提供了 PeriodicTimer 後就可以更方便的處理計時工作,這篇就來認識執行計時工作的幾種實作方式。
BackgroundService
先來幾個連結好好認識 BackgroundService
- 在 ASP.NET Core 中使用託管服務的背景工作 | Microsoft Learn
- 在微服務中使用 IHostedService 和 BackgroundService 類別實作背景工作 - .NET | Microsoft Learn
- 使用 .NET 6 開發 Windows Service-黑暗執行緒
- 如何使用.NET 6的IHostedService和BackgroundService? - 张飞洪[厦门] - 博客园
這裡就使用 BackgroundService 建立一個背景常駐服務,並且定時執行簡單的工作處理
namespace Sample.WebApplication.BackgroundServices;
/// <summary>
/// class SampleBackgroundService
/// </summary>
public class SampleBackgroundService : BackgroundService
{
/// <summary>
/// 執行間隔時間 60 sec
/// </summary>
private static readonly TimeSpan IntervalTime = TimeSpan.FromSeconds(60);
private readonly ILogger<SampleBackgroundService> _logger;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Initializes a new instance of the <see cref="SampleBackgroundService"/> class
/// </summary>
/// <param name="logger">The logger</param>
/// <param name="timeProvider">The timeProvider</param>
public SampleBackgroundService(ILogger<SampleBackgroundService> logger, TimeProvider timeProvider)
{
this._logger = logger;
this._timeProvider = timeProvider;
}
/// <summary>
/// Executes the stopping token
/// </summary>
/// <param name="stoppingToken">The stopping token</param>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
this._logger.LogInformation(
"{DateTimeNow} [{MachineName}] {TypeName} is starting.",
$"{this._timeProvider.GetLocalNow().DateTime:yyyy-MM-dd HH:mm:ss}",
Environment.MachineName,
this.GetType().Name);
while (stoppingToken.IsCancellationRequested is false)
{
// 放在前面則是先延遲 60 秒,然後再處理工作執行
await Task.Delay(IntervalTime, stoppingToken).ConfigureAwait(false);
this._logger.LogInformation(
"{DateTimeNow} [{MachineName}] {TypeName} Processing.",
$"{this._timeProvider.GetLocalNow().DateTime:yyyy-MM-dd HH:mm:ss}",
Environment.MachineName,
this.GetType().Name);
// 如果是放在後面,則是先處理工作後再延遲 60 秒
// await Task.Delay(IntervalTime, stoppingToken).ConfigureAwait(false);
}
}
/// <summary>
/// Stops the cancellation token
/// </summary>
/// <param name="cancellationToken">The cancellation token</param>
public override async Task StopAsync(CancellationToken cancellationToken)
{
this._logger.LogInformation(
"{DateTimeNow} [{MachineName}] {TypeName} is stopping.",
$"{this._timeProvider.GetLocalNow().DateTime:yyyy-MM-dd HH:mm:ss}",
Environment.MachineName,
this.GetType().Name);
await base.StopAsync(cancellationToken);
}
}
做好 SampleBackgroundService 後,記得要再去 Program.cs 裡註冊背景服務
// 註冊背景服務
builder.Services.AddHostedService<SampleBackgroundService>();
觀察 Web Application 執行,在程式啟動時就有寫入 log,然後於執行後每間隔 60 秒就會再寫 log,最後當 Web Application 停止服務時寫入 log
以上是使用 Task.Delay 的方式去完成間隔一段時間的計時處理方式,接下來就換成 PeriodicTimer 來試試看。
PeriodicTimer
這是在 .NET 6 以後所提供的類別,透過以下的連結認識這個類別
- 計時器 - .NET | Microsoft Learn
- PeriodicTimer 類別 (System.Threading) | Microsoft Learn
- C# PeriodicTimer - DEV Community
- .Net6 新特性 - PeriodicTimer - 异步化的定时器 - Hwj's blog
PeriodicTimer 與 Timer 的差異在於 PeriodicTimer 提供了 WaitForNextTickAsync(CancellationToken) 方法,是非同步等待處理,而不是使用 Timer 類別去指定 callback 的方式。
將 SampleBackgroundService 改使用 PeriodicTimer
namespace Sample.WebApplication.BackgroundServices;
/// <summary>
/// class SampleBackgroundService
/// </summary>
public class SampleBackgroundService : BackgroundService
{
/// <summary>
/// 間隔時間 60 sec
/// </summary>
private static readonly TimeSpan IntervalTime = TimeSpan.FromSeconds(60);
private readonly ILogger<SampleBackgroundService> _logger;
private readonly TimeProvider _timeProvider;
/// <summary>
/// Initializes a new instance of the <see cref="SampleBackgroundService"/> class
/// </summary>
/// <param name="logger">The logger</param>
/// <param name="timeProvider">The timeProvider</param>
public SampleBackgroundService(ILogger<SampleBackgroundService> logger, TimeProvider timeProvider)
{
this._logger = logger;
this._timeProvider = timeProvider;
}
/// <summary>
/// Executes the stopping token
/// </summary>
/// <param name="stoppingToken">The stopping token</param>
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
this._logger.LogInformation(
"{DateTimeNow} [{MachineName}] {TypeName} is starting.",
$"{this._timeProvider.GetLocalNow().DateTime:yyyy-MM-dd HH:mm:ss}",
Environment.MachineName,
this.GetType().Name);
// 使用 PeriodicTimer
using PeriodicTimer periodicTimer = new(IntervalTime);
while (await periodicTimer.WaitForNextTickAsync(stoppingToken) && stoppingToken.IsCancellationRequested is false)
{
this._logger.LogInformation(
"{DateTimeNow} [{MachineName}] {TypeName} Processing.",
$"{this._timeProvider.GetLocalNow().DateTime:yyyy-MM-dd HH:mm:ss}",
Environment.MachineName,
this.GetType().Name);
}
}
/// <summary>
/// Stops the cancellation token
/// </summary>
/// <param name="cancellationToken">The cancellation token</param>
public override async Task StopAsync(CancellationToken cancellationToken)
{
this._logger.LogInformation(
"{DateTimeNow} [{MachineName}] {TypeName} is stopping.",
$"{this._timeProvider.GetLocalNow().DateTime:yyyy-MM-dd HH:mm:ss}",
Environment.MachineName,
this.GetType().Name);
await base.StopAsync(cancellationToken);
}
}
修改後觀察 Web Application 執行,在程式啟動時就有寫入 log,然後於執行後每間隔 60 秒就會再寫 log,最後當 Web Application 停止服務時寫入 log
原本還想再帶點其他東西,不過這一篇就先到這裡。
以上
純粹是在寫興趣的,用寫程式、寫文章來抒解工作壓力