У меня есть служба 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 секунд...)
Sidenote не используйте Thread.Sleep в коде на основе задач. Всегда используйте Task.Delay.
Если вам нужно время, используйте DateTime.Now
. Если вы хотите периодически что-то повторять, используйте таймер. Задачи не являются задачами планировщика и не имеют понятия времени.
Не всегда используйте Task.Delay, но всегда избегайте Thread.Sleep. Task.Delay
сам использует объект таймера внутри себя. Вызов его несколько раз создает несколько объектов Timer, которые необходимо очистить от мусора. Если вы хотите делать что-то каждые 5 секунд, используйте System.Threading.Timer с периодом 5 с.
Что, если DoJob
занимает больше 5 секунд, это нормально?
Связано: Регулярно запускайте асинхронный метод с указанным интервалом.
Вам нужно выяснить, нормально ли одновременное выполнение нескольких DoJobs
. Если это нормально, используйте Timers.Timer или Threading.Timer, если нет, используйте PeriodicTimer.
Я проголосовал за повторное открытие, а затем за закрытое голосование, потому что подумал, что связанный вопрос не является дубликатом, а затем увидел, что ошибался.
Самый простой способ запускать и планировать задания — использовать такую библиотеку, как 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);
}
}
Да, но такое ощущение, будто вы предлагаете бензопилу, а ножниц может быть достаточно.
Обновил мой ответ без использования библиотеки, но рассчитав оставшуюся задержку.
@Ральф, ты уверен? Task.Delay
не подходит для многократного использования, поскольку каждый раз использует новый объект Timer. Сам объект Timer подходит только для простых операций. Однако нет никакой причины рассчитывать оставшуюся задержку для этих простых операций, достаточно просто использовать таймер.
@PanagiotisKanavos Я знал, но не знал о деталях реализации Task.Delay. Посмотрю код, если там есть что-то, что мне нужно знать.
Не используйте DateTime.UtcNow
для измерения временных интервалов. Ваше решение становится чувствительным к системным настройкам часов. Вместо этого используйте класс Секундомер.
Посмотрите на совершенно новый PeriodicTimer
var periodicTimer = new PeriodicTimer(TimeSpan.FromSeconds(5));
while (await periodicTimer.WaitForNextTickAsync(stoppingToken))
{
await DoJob();
}
Просто не ждите DoJob? Просто поместите возвращенную задачу в переменную и после задержки, когда вы захотите запустить ее снова, проверьте, не запущена ли она еще. Решите, что делать в этом случае. Если вы не ждете, как я предлагаю, проверьте на своем примере из реального мира вашу обработку исключений, которую затем необходимо изменить.