C# 调度程序后台服务中的异步计时器

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

我正在.NETCore中编写一个托管服务,它根据计时器在后台运行作业

目前,我必须同步运行代码,如下所示:

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();
    }
}