Выполнение асинхронной задачи каждые n секунд, включая время выполнения самой задачи

У меня есть служба Windows, в которой я хочу выполнять асинхронную задачу каждые n секунд, но время, в течение которого я жду задачу, должно быть включено в эти n секунд.

Вот краткий образец

CancellationToken stoppingToken = new();
while (!stoppingToken.IsCancellationRequested)
{
    await DoJob();
    await Task.Delay(5000);
}

и вот задача DoJob:

private async Task DoJob()
{
    Thread.Sleep(3000);
}

Если я зарегистрирую цикл и задачу, я увижу, что задача выполняется 3 секунды, а задержка после этого составляет 5 секунд.

Loop startet at 07.06.2024 12:01:46
Start Job at    07.06.2024 12:01:46
Ended Job at    07.06.2024 12:01:49 
Loop startet at 07.06.2024 12:01:54 
Start Job at    07.06.2024 12:01:54 
Ended Job at    07.06.2024 12:01:57 
Loop startet at 07.06.2024 12:02:02 
Start Job at    07.06.2024 12:02:02 
Ended Job at    07.06.2024 12:02:05 

Как я могу изменить это, чтобы задача запускалась каждые 5 секунд, ВКЛЮЧАЯ 3 секунды, которые занимает задача. (Конечно, в реальном мире время, необходимое для выполнения задачи, точно неизвестно.

И на втором этапе: как проверить, завершилась ли предыдущая задача, чтобы не запускать ее несколько раз. (Например, задача занимает 10 секунд, а задержка составляет всего 5 секунд...)

Просто не ждите DoJob? Просто поместите возвращенную задачу в переменную и после задержки, когда вы захотите запустить ее снова, проверьте, не запущена ли она еще. Решите, что делать в этом случае. Если вы не ждете, как я предлагаю, проверьте на своем примере из реального мира вашу обработку исключений, которую затем необходимо изменить.

Ralf 07.06.2024 12:15

Sidenote не используйте Thread.Sleep в коде на основе задач. Всегда используйте Task.Delay.

Ralf 07.06.2024 12:17

Если вам нужно время, используйте DateTime.Now. Если вы хотите периодически что-то повторять, используйте таймер. Задачи не являются задачами планировщика и не имеют понятия времени.

Panagiotis Kanavos 07.06.2024 12:24

Не всегда используйте Task.Delay, но всегда избегайте Thread.Sleep. Task.Delay сам использует объект таймера внутри себя. Вызов его несколько раз создает несколько объектов Timer, которые необходимо очистить от мусора. Если вы хотите делать что-то каждые 5 секунд, используйте System.Threading.Timer с периодом 5 с.

Panagiotis Kanavos 07.06.2024 12:28

Что, если DoJob занимает больше 5 секунд, это нормально?

Johnathan Barclay 07.06.2024 12:31

Вам нужно выяснить, нормально ли одновременное выполнение нескольких DoJobs. Если это нормально, используйте Timers.Timer или Threading.Timer, если нет, используйте PeriodicTimer.

JonasH 07.06.2024 14:24

Я проголосовал за повторное открытие, а затем за закрытое голосование, потому что подумал, что связанный вопрос не является дубликатом, а затем увидел, что ошибался.

Theodor Zoulias 08.06.2024 11:07
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
8
91
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Самый простой способ запускать и планировать задания — использовать такую ​​библиотеку, как Quartz.net of Hangfire (https://www.hangfire.io).

С помощью этих библиотек вы можете создать задание с выражением cron, чтобы запускать задание в определенное время или через определенные промежутки времени. Вы также можете отключить одновременное выполнение заданий в этих библиотеках.

Если вы хотите сделать это самостоятельно и не использовать библиотеку, вам следует рассчитать длину выполнения задачи. Это время выполнения вычитается из времени ожидания по умолчанию.

var intervalMilliseconds = 5000;
CancellationToken stoppingToken = new();
while (!stoppingToken.IsCancellationRequested)
{
    if (!isTaskRunning)
    {
        isTaskRunning = true;
        var taskStartTime = DateTime.UtcNow;
        await DoJob();
        var taskEndTime = DateTime.UtcNow;
        isTaskRunning = false;

        var elapsedTime = (taskEndTime - taskStartTime).TotalMilliseconds;               
        var remainingDelay = intervalMilliseconds - elapsedTime;

        if (remainingDelay > 0)
        {
            await Task.Delay((int)remainingDelay, stoppingToken);
        }
    }
    else
    {
        await Task.Delay(intervalMilliseconds, stoppingToken);
    } 
}

Да, но такое ощущение, будто вы предлагаете бензопилу, а ножниц может быть достаточно.

Ralf 07.06.2024 12:19

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

user7579919 07.06.2024 12:28

@Ральф, ты уверен? Task.Delay не подходит для многократного использования, поскольку каждый раз использует новый объект Timer. Сам объект Timer подходит только для простых операций. Однако нет никакой причины рассчитывать оставшуюся задержку для этих простых операций, достаточно просто использовать таймер.

Panagiotis Kanavos 07.06.2024 12:29

@PanagiotisKanavos Я знал, но не знал о деталях реализации Task.Delay. Посмотрю код, если там есть что-то, что мне нужно знать.

Ralf 07.06.2024 12:33

Не используйте DateTime.UtcNow для измерения временных интервалов. Ваше решение становится чувствительным к системным настройкам часов. Вместо этого используйте класс Секундомер.

Theodor Zoulias 07.06.2024 13:16
Ответ принят как подходящий

Посмотрите на совершенно новый PeriodicTimer

var periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(5));

while (await periodicTimer.WaitForNextTickAsync(stoppingToken))
{
    await DoJob();    
}

Другие вопросы по теме