运行 ASP.NET 核心中每 5 分钟一次的异步托管服务
Run async hosted service every 5 minutes in ASP.NET Core
后台服务的 ASP.NET 核心 docs 展示了一些实施示例。
有一个在 timer, though it's synchronous. There's another example which is asynchronous, for starting a service with a scoped dependency 上启动服务的示例。
我需要同时执行这两项操作:每 5 分钟启动一次服务,并且它具有范围内的依赖项。没有这方面的例子。
我结合了这两个示例,但我不确定将 Timer
与异步 TimerCallback
.
一起使用的安全方法
例如
public class MyScheduler : IHostedService
{
private Timer? _timer;
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;
public void Dispose() => _timer?.Dispose();
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer((object? state) => {
using var scope = _serviceScopeFactory.CreateScope();
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(cancellationToken); // <------ problem
}), null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) {
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
}
计时器需要一个同步回调,所以问题出在await
。调用异步服务的安全方法是什么?
使用异步处理程序创建一个事件并每隔一段时间引发一次。可以等待事件处理程序
public class MyScheduler : IHostedService {
private Timer? _timer;
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;
public void Dispose() => _timer?.Dispose();
public Task StopAsync(CancellationToken cancellationToken) {
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public Task StartAsync(CancellationToken cancellationToken) {
performAction += onPerformAction; //subscribe to event
ScopedServiceArgs args = new ScopedServiceArgs {
ServiceScopeFactory = _serviceScopeFactory,
CancellationToken = cancellationToken
};
_timer = new Timer((object? state) => performAction(this, args), //<-- raise event
null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private event EventHandler<ScopedServiceArgs> performAction = delegate { };
private async void onPerformAction(object sender, CancellationArgs args) {
using IServiceScope scope = args.ServiceScopeFactory.CreateScope();
IMyService myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(args.CancellationToken);
}
class ScopedServiceArgs : EventArgs {
public IServiceScopeFactory ServiceScopeFactory {get; set;}
public CancellationToken CancellationToken {get; set;}
}
}
使用BackgroundService
代替IHostedService
public class MyScheduler : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Option 1
while (!stoppingToken.IsCancellationRequested)
{
// do async work
using (var scope = _serviceScopeFactory.CreateScope())
{
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(stoppingToken);
}
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
// Option 2 (.NET 6)
var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// do async work
// ...as above
}
}
}
后台服务的 ASP.NET 核心 docs 展示了一些实施示例。
有一个在 timer, though it's synchronous. There's another example which is asynchronous, for starting a service with a scoped dependency 上启动服务的示例。
我需要同时执行这两项操作:每 5 分钟启动一次服务,并且它具有范围内的依赖项。没有这方面的例子。
我结合了这两个示例,但我不确定将 Timer
与异步 TimerCallback
.
例如
public class MyScheduler : IHostedService
{
private Timer? _timer;
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;
public void Dispose() => _timer?.Dispose();
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer((object? state) => {
using var scope = _serviceScopeFactory.CreateScope();
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(cancellationToken); // <------ problem
}), null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken) {
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
}
计时器需要一个同步回调,所以问题出在await
。调用异步服务的安全方法是什么?
使用异步处理程序创建一个事件并每隔一段时间引发一次。可以等待事件处理程序
public class MyScheduler : IHostedService {
private Timer? _timer;
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;
public void Dispose() => _timer?.Dispose();
public Task StopAsync(CancellationToken cancellationToken) {
_timer?.Change(Timeout.Infinite, 0);
return Task.CompletedTask;
}
public Task StartAsync(CancellationToken cancellationToken) {
performAction += onPerformAction; //subscribe to event
ScopedServiceArgs args = new ScopedServiceArgs {
ServiceScopeFactory = _serviceScopeFactory,
CancellationToken = cancellationToken
};
_timer = new Timer((object? state) => performAction(this, args), //<-- raise event
null, TimeSpan.Zero, TimeSpan.FromSeconds(5));
return Task.CompletedTask;
}
private event EventHandler<ScopedServiceArgs> performAction = delegate { };
private async void onPerformAction(object sender, CancellationArgs args) {
using IServiceScope scope = args.ServiceScopeFactory.CreateScope();
IMyService myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(args.CancellationToken);
}
class ScopedServiceArgs : EventArgs {
public IServiceScopeFactory ServiceScopeFactory {get; set;}
public CancellationToken CancellationToken {get; set;}
}
}
使用BackgroundService
代替IHostedService
public class MyScheduler : BackgroundService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
public MyScheduler(IServiceScopeFactory serviceScopeFactory) => _serviceScopeFactory = serviceScopeFactory;
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
// Option 1
while (!stoppingToken.IsCancellationRequested)
{
// do async work
using (var scope = _serviceScopeFactory.CreateScope())
{
var myService = scope.ServiceProvider.GetRequiredService<IMyService>();
await myService.Execute(stoppingToken);
}
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
}
// Option 2 (.NET 6)
var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
while (await timer.WaitForNextTickAsync(stoppingToken))
{
// do async work
// ...as above
}
}
}