C# 调度程序后台服务中的异步计时器
我正在.NETCore中编写一个托管服务,它根据计时器在后台运行作业 目前,我必须同步运行代码,如下所示:C# 调度程序后台服务中的异步计时器,c#,asynchronous,timer,.net-core,background-service,C#,Asynchronous,Timer,.net Core,Background Service,我正在.NETCore中编写一个托管服务,它根据计时器在后台运行作业 目前,我必须同步运行代码,如下所示: public override Task StartAsync(CancellationToken cancellationToken) { this._logger.LogInformation("Timed Background Service is starting."); this._timer = new Timer(ExecuteTask, null, Tim
public override Task StartAsync(CancellationToken cancellationToken)
{
this._logger.LogInformation("Timed Background Service is starting.");
this._timer = new Timer(ExecuteTask, null, TimeSpan.Zero,
TimeSpan.FromSeconds(30));
return Task.CompletedTask;
}
private void ExecuteTask(object state)
{
this._logger.LogInformation("Timed Background Service is working.");
using (var scope = _serviceProvider.CreateScope())
{
var coinbaseService = scope.ServiceProvider.GetRequiredService<CoinbaseService>();
coinbaseService.FinalizeMeeting();
}
}
public override Task StartAsync(CancellationToken CancellationToken)
{
这是._logger.LogInformation(“定时后台服务正在启动”);
this.\u timer=new timer(ExecuteTask,null,TimeSpan.Zero,
时间跨度从秒(30));
返回Task.CompletedTask;
}
私有void ExecuteTask(对象状态)
{
这是._logger.LogInformation(“定时后台服务正在工作”);
使用(var scope=\u serviceProvider.CreateScope())
{
var coinbaseService=scope.ServiceProvider.GetRequiredService();
coinbaseService.FinalizeMeeting();
}
}
我想在计时器上运行这个异步,但我不想使用fire and forget运行异步,因为它可能会在代码中造成竞争条件。
e、 g(订阅计时器。已用时间事件)
有没有一种方法可以在定时计划中利用异步代码而不执行fire and forget异步的全部目的是不阻塞主线程。但这已经是一个后台线程了,所以这并不重要,除非它是一个ASP.NET核心应用程序。这是唯一重要的一次,因为线程池是有限的,耗尽线程池意味着无法处理更多的请求
如果您确实想运行它async
,只需将其设置为async
:
private async void ExecuteTask(object state)
{
//await stuff here
}
是的,我知道你说你不想“开火然后忘记”,但事情真的就是这样:它们是开火然后忘记。因此,您的ExecuteTask
方法将被调用,并且它(1)是否仍在运行,或者(2)是否失败,都不关心(或检查)无论是否运行此async
都是如此。
您可以通过将ExecuteTask
方法中的所有内容包装到try
/catch
块中,并确保将其记录在某个位置,以便知道发生了什么,从而减轻故障
另一个问题是知道它是否仍在运行(这也是一个问题,即使您没有运行async
)。还有一种方法可以缓解这种情况:
private Task doWorkTask;
private void ExecuteTask(object state)
{
doWorkTask = DoWork();
}
private async Task DoWork()
{
//await stuff here
}
在这种情况下,计时器只是启动任务。但区别在于,您保留了对任务的引用。这将允许您检查代码中任何其他位置的任务的状态。例如,如果要验证是否已完成,可以查看doWorkTask.IsCompleted
或doWorkTask.Status
此外,当应用程序关闭时,您可以使用:
await doWorkTask;
在关闭应用程序之前确保任务已完成。否则,线程将被终止,可能使事情处于不一致的状态。请注意,如果在DoWork()
中发生未处理的异常,则使用await-DoWork task
将引发异常
在开始下一个任务之前,最好先验证上一个任务是否已完成。对于那些正在寻找阻止同时运行任务的完整示例的人来说。
基于@Gabriel Luci的回答和评论
请随时发表评论,以便我可以更正它
/// <summary>
/// Based on Microsoft.Extensions.Hosting.BackgroundService https://github.com/aspnet/Extensions/blob/master/src/Hosting/Abstractions/src/BackgroundService.cs
/// Additional info: - https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual-studio#timed-background-tasks
/// - https://stackoverflow.com/questions/53844586/async-timer-in-scheduler-background-service
/// </summary>
public abstract class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
public TimedHostedService(ILogger<TimedHostedService> logger)
{
_logger = logger;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is starting.");
_timer = new Timer(ExecuteTask, null, TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(-1));
return Task.CompletedTask;
}
private void ExecuteTask(object state)
{
_timer?.Change(Timeout.Infinite, 0);
_executingTask = ExecuteTaskAsync(_stoppingCts.Token);
}
private async Task ExecuteTaskAsync(CancellationToken stoppingToken)
{
await RunJobAsync(stoppingToken);
_timer.Change(TimeSpan.FromSeconds(30), TimeSpan.FromMilliseconds(-1));
}
/// <summary>
/// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task
/// </summary>
/// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
/// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
protected abstract Task RunJobAsync(CancellationToken stoppingToken);
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
_logger.LogInformation("Timed Background Service is stopping.");
_timer?.Change(Timeout.Infinite, 0);
// 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 void Dispose()
{
_stoppingCts.Cancel();
_timer?.Dispose();
}
}
//
///基于Microsoft.Extensions.Hosting.BackgroundServicehttps://github.com/aspnet/Extensions/blob/master/src/Hosting/Abstractions/src/BackgroundService.cs
///其他资料:-https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.2&tabs=visual studio#定时后台任务
/// - https://stackoverflow.com/questions/53844586/async-timer-in-scheduler-background-service
///
公共抽象类TimedHostedService:IHostedService,IDisposable
{
专用只读ILogger\u记录器;
私人定时器;
私有任务(executingTask);;
私有只读CancellationTokenSource _stoppingCts=new CancellationTokenSource();
公共TimedHostedService(ILogger记录器)
{
_记录器=记录器;
}
公共任务StartSync(CancellationToken CancellationToken)
{
_logger.LogInformation(“定时后台服务正在启动”);
_计时器=新计时器(ExecuteTask,null,TimeSpan.FromSeconds(30),TimeSpan.From毫秒(-1));
返回Task.CompletedTask;
}
私有void ExecuteTask(对象状态)
{
_计时器?更改(Timeout.Infinite,0);
_executingTask=ExecuteTaskAsync(_stoppingCts.Token);
}
专用异步任务ExecuteTaskAsync(CancellationToken stoppingToken)
{
等待runjobsync(stoppingToken);
_timer.Change(TimeSpan.FromSeconds(30),TimeSpan.fromsecoms(-1));
}
///
///启动时调用此方法。实现应返回任务
///
///调用时触发。
///表示长时间运行的操作的。
受保护的抽象任务RunJobAsync(CancellationToken stoppingToken);
公共虚拟异步任务StopAsync(CancellationToken CancellationToken)
{
_logger.LogInformation(“定时后台服务正在停止”);
_计时器?更改(Timeout.Infinite,0);
//没有开始就叫停
如果(_executingTask==null)
{
返回;
}
尝试
{
//执行方法的信号消除
_stoppingCts.Cancel();
}
最后
{
//等待任务完成或停止令牌触发
wait Task.wheny(_executingTask,Task.Delay(Timeout.Infinite,cancellationToken));
}
}
公共空间处置()
{
_stoppingCts.Cancel();
protected override async Task RunJobAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken)
{
DbContext context = serviceProvider.GetRequiredService<DbContext>();
}
public abstract class TimedHostedService : IHostedService, IDisposable
{
private readonly ILogger _logger;
private Timer _timer;
private Task _executingTask;
private readonly CancellationTokenSource _stoppingCts = new CancellationTokenSource();
IServiceProvider _services;
public TimedHostedService(IServiceProvider services)
{
_services = services;
_logger = _services.GetRequiredService<ILogger<TimedHostedService>>();
}
public Task StartAsync(CancellationToken cancellationToken)
{
_timer = new Timer(ExecuteTask, null,FirstRunAfter, TimeSpan.FromMilliseconds(-1));
return Task.CompletedTask;
}
private void ExecuteTask(object state)
{
_timer?.Change(Timeout.Infinite, 0);
_executingTask = ExecuteTaskAsync(_stoppingCts.Token);
}
private async Task ExecuteTaskAsync(CancellationToken stoppingToken)
{
try
{
using (var scope = _services.CreateScope())
{
await RunJobAsync(scope.ServiceProvider, stoppingToken);
}
}
catch (Exception exception)
{
_logger.LogError("BackgroundTask Failed", exception);
}
_timer.Change(Interval, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
/// This method is called when the <see cref="IHostedService"/> starts. The implementation should return a task
/// </summary>
/// <param name="serviceProvider"></param>
/// <param name="stoppingToken">Triggered when <see cref="IHostedService.StopAsync(CancellationToken)"/> is called.</param>
/// <returns>A <see cref="Task"/> that represents the long running operations.</returns>
protected abstract Task RunJobAsync(IServiceProvider serviceProvider, CancellationToken stoppingToken);
protected abstract TimeSpan Interval { get; }
protected abstract TimeSpan FirstRunAfter { get; }
public virtual async Task StopAsync(CancellationToken cancellationToken)
{
_timer?.Change(Timeout.Infinite, 0);
// 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 void Dispose()
{
_stoppingCts.Cancel();
_timer?.Dispose();
}
}