代码之家  ›  专栏  ›  技术社区  ›  johnny 5

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

  •  0
  • johnny 5  · 技术社区  · 7 年前

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

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

    e、 g(签署 timer.Elapsed (活动)

    有没有一种方法可以在不执行fire和forget的情况下按定时计划利用异步代码

    1 回复  |  直到 7 年前
        1
  •  20
  •   g36    7 年前

    对于那些正在寻找阻止同时运行任务的完整示例的人。 基于@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
  •  7
  •   Gabriel Luci    7 年前

    目的 async 就是不占用主线程。但这已经是一个后台线程了,所以这并不重要,除非它是一个ASP.NET核心应用程序。这是唯一重要的一次,因为线程池是有限的,耗尽线程池意味着无法处理更多的请求。

    异步的 异步的

    private async void ExecuteTask(object state)
    {
        //await stuff here
    }
    

    是的,我知道你说你不想“开火然后忘记”,但是 ExecuteTask 方法将被调用,并且不关心(或检查)它是否(1)仍在运行或(2)是否失败。 这是真的,无论你运行这个 或者不是。

    执行任务 a中的方法 try / catch 阻止并确保它记录在某个地方,以便您知道发生了什么。

    另一个问题是知道它是否还在运行(这也是一个问题,即使你没有运行) 异步的 ).还有一种方法可以缓解这种情况:

    private Task doWorkTask;
    
    private void ExecuteTask(object state)
    {
        doWorkTask = DoWork();
    }
    
    private async Task DoWork()
    {
        //await stuff here
    }
    

    Task . 这将允许您检查服务器的状态 任务 代码中的任何其他地方。例如,如果要验证是否已完成,可以查看 doWorkTask.IsCompleted 或 doWorkTask.Status .

    此外,当应用程序关闭时,您可以使用:

    await doWorkTask;
    

    在关闭应用程序之前确保任务已完成。否则,线程将被终止,可能使事情处于不一致的状态。请注意,使用 await doWorkTask DoWork() .

    在开始下一个任务之前,最好先验证上一个任务是否已完成。

        3
  •  2
  •   Seagull    5 年前

    这是一个基于先前回答的改进版本。改进:

    1. 对于执行的每个任务,都会创建一个作用域,因此您可以访问RunJobAsync中的任何作用域服务
    2. 可以在继承的类中指定间隔和初始任务执行时间。

    访问作用域服务示例

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