.net core 基於 IHostedService 實現定時任務 ...
.net core 基於 IHostedService 實現定時任務
Intro
從 .net core 2.0 開始,開始引入 IHostedService
,可以通過 IHostedService
來實現後臺任務,但是只能在 WebHost
的基礎上使用。從 .net core 2.1 開始微軟引入通用主機(Generic Host
),使得我們可以在不使用 Web 的情況下,也可以使用 IHostedService
來實現 定時任務/Windows服務/後臺任務,並且引入了一個 BackgroundService
抽象類來更方便的創建後臺任務。
IHostedService
IHostedService
後臺任務的執行與應用程式(就此而言,為主機或微服務)的生存期相協調。 當應用程式啟動時註冊任務,當應用程式關閉時,有機會執行某些正常操作或清理。
始終可以啟動後臺線程來運行任何任務,而無需使用 IHostedService
。 不同之處就在於應用的關閉時間,此時會直接終止線程,而沒有機會執行正常的清理操作。
當註冊 IHostedService
時,.NET Core 會在應用程式啟動和停止期間分別調用 IHostedService
類型的 StartAsync()
和 StopAsync()
方法。 具體而言,即在伺服器已啟動並已觸發 IApplicationLifetime.ApplicationStarted
後調用 start。
namespace Microsoft.Extensions.Hosting
{
//
// Summary:
// Defines methods for objects that are managed by the host.
public interface IHostedService
{
//
// Summary:
// Triggered when the application host is ready to start the service.
Task StartAsync(CancellationToken cancellationToken);
//
// Summary:
// Triggered when the application host is performing a graceful shutdown.
Task StopAsync(CancellationToken cancellationToken);
}
}
可以從頭開始創建自定義托管服務類並實現 IHostedService
,因為在使用 .NET Core 2.0 時需執行這些操作。
但是,由於大多數後臺任務在取消令牌管理和其他典型操作方面都有類似的需求,因此 .net core 2.1 有一個非常方便且可以從中進行派生的抽象基類,BackgroundService
定義如下:
// Copyright (c) .NET Foundation. Licensed under the Apache License, Version 2.0.
/// <summary>
/// Base class for implementing a long running <see cref="IHostedService"/>.
/// </summary>
public abstract class BackgroundService : IHostedService, IDisposable
{
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts =
new CancellationTokenSource();
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it,
// this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
// Stop called without start
if (_executingTask == null)
{
return;
}
try
{
// Signal cancellation to the executing method
_stoppingCts.Cancel();
}
finally
{
// Wait until the task completes or the stop token triggers
await Task.WhenAny(_executingTask, Task.Delay(Timeout.Infinite, cancellationToken));
}
}
public virtual void Dispose()
{
_stoppingCts.Cancel();
}
}
實現一個的後臺定時任務
基於上面的信息,我們可以基於 IHostedService
實現一個簡單的後臺定時任務服務,
public abstract class ScheduledService : IHostedService, IDisposable
{
private readonly Timer _timer;
private readonly TimeSpan _period;
protected readonly ILogger Logger;
protected ScheduledService(TimeSpan period, ILogger logger)
{
Logger = logger;
_period = period;
_timer = new Timer(Execute, null, Timeout.Infinite, 0);
}
public void Execute(object state = null)
{
try
{
Logger.LogInformation("Begin execute service");
ExecuteAsync().Wait();
}
catch (Exception ex)
{
Logger.LogError(ex, "Execute exception");
}
finally
{
Logger.LogInformation("Execute finished");
}
}
protected abstract Task ExecuteAsync();
public virtual void Dispose()
{
_timer?.Dispose();
}
public Task StartAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Service is starting.");
_timer.Change(TimeSpan.FromSeconds(SecurityHelper.Random.Next(10)), _period);
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
}
基於上面這個基於Timer實現的後臺定時任務類實現一個定時任務:
public class RemoveOverdueReservationService : ScheduledService
{
public RemoveOverdueReservationService(ILogger<RemoveOverduedReservtaionService> logger) : base(TimeSpan.FromDays(1), logger)
{ }
protected override Task ExecuteAsync()
{
return DependencyResolver.Current.TryInvokeServiceAsync<IEFRepository<ReservationDbContext, Reservation>>(reservationRepo =>
{
return reservationRepo.DeleteAsync(reservation => reservation.ReservationStatus == 0 && (reservation.ReservationForDate < DateTime.Today.AddDays(-3)));
});
}
}
這個類實現的是每天執行一次,刪除三天前狀態為待審核的預約,完整實現代碼:https://github.com/WeihanLi/ActivityReservation/blob/dev/ActivityReservation.Helper/Services/RemoveOverduedReservtaionService.cs
在程式啟動的時候註冊服務:
services.AddHostedService<RemoveOverduedReservtaionService>();
執行日誌:
通過日誌可以看到我們的定時任務確實是每天執行一次,這樣我們的定時任務就算是簡單的完成了。
Reference
- https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services
- https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/background-tasks-with-ihostedservice
- https://docs.microsoft.com/zh-cn/dotnet/standard/microservices-architecture/multi-container-microservice-net-applications/background-tasks-with-ihostedservice
- https://www.stevejgordon.co.uk/asp-net-core-2-ihostedservice
- https://github.com/WeihanLi/ActivityReservation