我正在.Net-Core中编写一个托管服务,该服务基于计时器在后台运行作业。
当前,我必须像这样同步运行代码:
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();
}
}
我想在计时器上运行此Async,但是我不想使用fire来运行async并忘记了,因为这可能会导致代码中出现竞争情况。
例如(订阅timer.Elapsed
事件)
有没有一种方法可以按时利用时间表上的异步代码,而无需执行触发操作而忘记
答案 0 :(得分:1)
async
的全部目的是不占用主线程。但这已经是一个后台线程,所以实际上并没有关系-除非它是ASP.NET Core应用程序。这是唯一重要的时间,因为线程池有限,耗尽它意味着无法再处理更多请求。
如果您真的想运行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
}
在这种情况下,您的计时器仅启动任务。但是不同之处在于,您保留对Task
的引用。这样一来,您就可以在代码中的其他任何地方检查Task
的状态。例如,如果要验证是否已完成,则可以查看doWorkTask.IsCompleted
或doWorkTask.Status
。
此外,当您的应用程序关闭时,您可以使用:
await doWorkTask;
确保在关闭应用程序之前任务已完成。否则,该线程将被杀死,可能会使事物处于不一致状态。请注意,如果await doWorkTask
中发生未处理的异常,则使用DoWork()
会引发异常。
在开始下一个任务之前验证上一个任务是否已完成也是一个好主意。
答案 1 :(得分:0)
对于那些正在寻找可以防止同时运行任务的完整示例的人。 基于@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();
}
}
答案 2 :(得分:0)
这是基于以前的回复的改进版本。改进:
访问范围服务示例
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();
}
}