Асинхронный таймер в фоновой службе планировщика - PullRequest
0 голосов
/ 19 декабря 2018

Я пишу размещенный сервис в .Net-Core, который запускает задание в фоновом режиме, основываясь на таймере.

В настоящее время мне приходится кодировать синхронно, например:

Я бы хотел запустить этот Async по таймеру, но я не хочу запускать async с использованием fire и забыть, потому что мой это может вызвать условия гонки в моем коде.напр. (подписка на событие timer.Elapsed)

Есть ли способ использовать асинхронный код по расписанию без выполнения команды fire и забыть

Ответы [ 2 ]

0 голосов
/ 19 июня 2019

Для тех, кто ищет полный пример, который мешает запускать задачи одновременно.Основано на ответе и комментариях @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
    ///                  - /12371494/asinhronnyi-taimer-v-fonovoi-sluzhbe-planirovschika
    /// </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();
        }
    }
0 голосов
/ 19 декабря 2018

Цель 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().

Также, возможно, хорошей идеей будет проверить, завершено ли предыдущее задание, прежде чем запускать следующее.

...