С++, как вызвать функцию в определенное время суток

Мне нужно выполнить функцию один раз в определенное время дня, как в этом псевдокоде:

while(true)
{
    now = Now();
    
    if (now == 16:00 or now == 22:00)
    {
        call_function();
    }
}

Я использую chrono Timepoints довольно часто, но всегда для относительных арифметических сравнений, например:

if (now >= prev_ts + std::chrono::minutes(30))
{
    do_something();
    prev_ts = now;

и это не сработает, потому что первый вызов произойдет немедленно, а мне нужно, чтобы это произошло в следующие 16:00.

Каков самый чистый способ добиться этого с помощью C++23?

Что меня смущает, так это то, что меня не волнуют даты и не волнуют смещения. Мне нужно что-то, что является просто объектом времени, а не датой и временем.

Забудьте все это. Напишите код, который вам нужно выполнить один раз, и ваша операционная система запустит вашу программу в запланированное время с помощью cron (Linux), планировщика задач (Windows) или любого другого механизма, который использует ваша система.

Tangentially Perpendicular 17.06.2024 01:20

@TangentiallyPerpendicle это не отдельный процесс. Данные (из функции) должны быть доступны остальной части приложения.

intrigued_66 17.06.2024 01:25

Если есть какая-то причина, по которой запланированное задание не работает для вас, вам необходимо объяснить это в своем вопросе. Тем не менее, круглосуточно работающий процесс, проверяющий время каждые несколько микросекунд, — это очень плохая практика. Он тратит время процессора и заряд батареи (если это актуально) и может даже не работать, если ОС приостанавливает процесс в периоды простоя.

Tangentially Perpendicular 17.06.2024 01:33

Самый простой и понятный вариант — использовать std::time с std::localtime_r из <ctime>. Это позволит разделить время на часы, минуты и секунды намного проще, чем что-либо в библиотеке <chrono>. Просто периодически проверяйте время и спите.

paddy 17.06.2024 01:33

Есть ли причина, по которой это помечено как c++23?

catnip 17.06.2024 01:45

@catnip, чтобы показать ответы на C++23, приветствуются, потому что после этого хрон стал намного лучше (c++20?).

intrigued_66 17.06.2024 01:50

Начиная с C++11 (я думаю), вы можете рассмотреть возможность использования std::sleep_for() или std::sleep_until() (при запуске определите, как далеко в будущем вы хотите выполнить требуемое действие, и спите столько же). Загвоздка в том, что они переводят текущий поток в спящий режим — если вашей программе нужно делать другие вещи во время ожидания, вам нужно будет явно запланировать эти «другие дела» или выполнить их в другом потоке. Возможно, было бы (ммм) проще запланировать задачу cron/scheduler в вашей системе, чтобы сигнализировать вашей программе, когда это необходимо (и написать код для ответа на этот сигнал).

Peter 17.06.2024 04:11

Вы ищете выполнение по местному времени или по всемирному координированному времени? Это имеет значение, поскольку местное время не гарантируется таким же, как UTC.

Howard Hinnant 17.06.2024 04:14

@Tangentially Perpendular - есть причины, по которым вы не можете использовать планировщик ОС. Например, у меня есть код в DLL, загружаемый другим приложением, который необходимо выполнять каждую пятницу в 16:00.

Gene 18.06.2024 08:01
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
9
198
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете рассчитать следующее наступление целевого времени и использовать std::this_thread::sleep_until, чтобы дождаться достижения этого времени.

Рассчитайте следующее появление целевого времени (например, 16:00 и 22:00), затем используйте std::this_thread::sleep_until, чтобы дождаться следующего целевого времени, и, наконец, вызовите функцию в указанное время.

#include <iostream>
#include <chrono>
#include <thread>

void call_function() {
    std::cout << "Function called at " << std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) << std::endl;
}

std::chrono::system_clock::time_point get_next_target_time(int hour, int minute) {
    using namespace std::chrono;

    auto now = system_clock::now();
    auto now_time_t = system_clock::to_time_t(now);
    auto now_tm = *std::localtime(&now_time_t);

    now_tm.tm_hour = hour;
    now_tm.tm_min = minute;
    now_tm.tm_sec = 0;

    auto target_time = system_clock::from_time_t(std::mktime(&now_tm));

    if (target_time <= now) {
        now_tm.tm_mday += 1;  // Schedule for the next day
        target_time = system_clock::from_time_t(std::mktime(&now_tm));
    }

    return target_time;
}

int main() {
    while (true) {
        auto next_16 = get_next_target_time(16, 0);  // Next 16:00
        auto next_22 = get_next_target_time(22, 0);  // Next 22:00
        auto next_time = std::min(next_16, next_22); // Get the nearest time

        std::this_thread::sleep_until(next_time); // Wait until the next target time
        call_function();
    }
    return 0;
}
Ответ принят как подходящий

Это вопрос, на который можно ответить несколькими способами. Какой путь правильный, зависит от потребностей приложения.

  1. Время суток указано по местному времени или по UTC?
  2. Если местное время, то это текущий местный часовой пояс компьютера или часовой пояс указан каким-либо другим способом, например, идентификатором IANA?
  3. Если сейчас текущее местное время, является ли устройство мобильным?
  4. Если это текущее местное время, должно ли приложение работать в течение нескольких месяцев и автоматически обновляться по мере установки на устройство новых версий базы данных часовых поясов IANA?

В зависимости от ответов на эти вопросы ответ на вопрос ФП варьируется от очень простого до относительно сложного. Независимо от того, насколько это сложно, <chrono> в C++23 справится с этим.

В своих ответах ниже я использую директивы function-local using, чтобы сократить многословие, которое может быть вызвано повторением std::chrono:: во многих местах. Сохраняя директивы using в области функций, я избегаю загрязнения глобального пространства имен. Однако, если это не ваш стиль, не стесняйтесь добавлять std::chrono:: непосредственно к тем символам, которые в противном случае этого требуют.

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

Время суток указывается в формате UTC:

#include <chrono>
#include <iostream>
#include <thread>

// UTC time

template <class F>
void
execute_at_time_of_day(std::chrono::system_clock::duration tod, F f)
{
    using namespace std::chrono;

    auto now = system_clock::now();
    auto time_to_execute = floor<days>(now) + tod;
    if (time_to_execute < now)
        time_to_execute += days{1};
    while (true)
    {
        std::this_thread::sleep_until(time_to_execute);
        f();
        time_to_execute += days{1};
    }
}

int
main()
{
    using namespace std::chrono;
    std::thread{[]()
    {
        execute_at_time_of_day(16h, []() {std::cout << "hi 1\n";});}
    }.detach();
    std::thread{[t]()
    {
        execute_at_time_of_day(22h, []() {std::cout << "hi 2\n";});}
    }.detach();
    while (true) {}
}

Примечания:

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

Я также выделил отдельный поток для каждой пары время суток/функция, чтобы было проще иметь некоторое количество пар, кроме двух.

Время суток (UTC) определяется длительностью хроно, которая может быть грубой, как часы, или точной, как system_clock::duration, что на практике не менее микросекунд. Например, если бы я хотел изменить 16:00 на 16:05:34.500, то в main я бы изменил 16h на 16h + 5min + 34s + 500ms.

В этом простейшем случае execute_at_time_of_day сначала получает системное время, сохраняя его в переменной now. Это время указывается как Unix Time (время с 1970-01-01 00:00:00 UTC, исключая дополнительные секунды).

Выражение floor<days>(now) усекает текущее время до точности дней (округляя вниз). Фактически это дата UTC, которая также может служить отсчетом времени до полуночи, с которой начинается эта дата UTC.

К дате UTC добавляется переданное время суток. Это желаемое время для следующего выполнения функции f. Если это время уже в прошлом, добавляется 1 день.

В цикле мы спим до желаемого времени для выполнения функции. Затем выполняем функцию. Затем мы вычисляем время следующего выполнения функции, добавляя 1 день.

Время суток местное / Стационарное устройство

Если в качестве времени суток указано местное время устройства, execute_at_time_of_day становится немного сложнее.

// Local time / Stationary device

template <class F>
void
execute_at_time_of_day(std::chrono::system_clock::duration local_tod, F f)
{
    using namespace std::chrono;

    auto zone = current_zone();
    auto now_utc = system_clock::now();
    auto time_to_execute = zone->to_local(now_utc);
    time_to_execute = floor<days>(time_to_execute) + local_tod;
    if (zone->to_sys(time_to_execute) < now_utc)
        time_to_execute += days{1};
    while (true)
    {
        std::this_thread::sleep_until(zone->to_sys(time_to_execute));
        f();
        time_to_execute += days{1};
    }
}

Примечания:

Выражение current_zone() возвращает time_zone const*, который относится к текущему часовому поясу устройства. Это можно использовать для перевода между UTC и местным временем. Результат сохраняется в переменной zone.

Если вместо текущего местного часового пояса устройства вам нужен конкретный часовой пояс IANA, вы можете изменить эту строку:

auto zone = current_zone();

чтобы (например):

auto zone = locate_zone("Europe/London");

В этой версии execute_at_time_of_daytime_to_execute вычисляется по местному времени, а не по UTC. Это делается путем перевода текущего времени на местное время с помощью zone. Затем (как и раньше) execute_at_time_of_day устанавливается с точностью до дней и к нему добавляется желаемое время суток. А если это время уже в прошлом, добавляются сутки.

Проверка «в прошлом» выполняется в формате UTC, а не по местному времени, поскольку время UTC, как известно, является монотонной шкалой, в отличие от местного времени, которое может скакать из-за корректировок перехода на летнее время (или любых других корректировок, инициированных политиками).

В цикле time_to_execute переводится в формат UTC для оператора sleep_until. sleep_until не сможет скомпилироваться, если указано местное время. Это снова мотивировано немонотонностью местного времени.

После вызова функции время следующего выполнения вычисляется по местному времени, а не по UTC. Это сохраняет местное время суток даже за пределами летнего времени. Т.е. следующий вызов функции обычно происходит через 24 часа, но может быть и через 23 или 25 часов, в зависимости от правил перехода на летнее время. Если бы это вычисление было выполнено в формате UTC, следующий вызов функции всегда будет ровно через 24 часа.

Возможно (но маловероятно), что указанное местное время имеет неуникальное сопоставление с UTC. Это может произойти, когда (например) корректировка смещения UTC из-за перехода на летнее время пропускает местное время или создает два из них. Если это произойдет, будет выброшено исключение. Если исключение нежелательно, есть способы сделать это. Но я оставлю это для другого вопроса.

Если это устройство иногда может быть перенесено в другой часовой пояс, и если функция может выполняться в неподходящее время, когда это происходит, измените эту строку:

std::this_thread::sleep_until(zone->to_sys(time_to_execute));

к:

std::this_thread::sleep_until(current_zone()->to_sys(time_to_execute));

Т.е. повторно запрашивайте местный часовой пояс на каждой итерации цикла.

Время суток местное/мобильное устройство.

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

В приведенном ниже решении поток ничего не делает, кроме проверки того, изменился ли местный часовой пояс. И когда оно меняется, переменная глобальной области, содержащая time_zone const*, сообщает, какой локальный часовой пояс между потоками, а mutex и condition_variable глобальной области синхронизируют эту связь потокобезопасным способом:

// Local time / For mobile device

auto zone = std::chrono::current_zone();
std::mutex zone_mutex;
std::condition_variable zone_cv;

void
check_for_zone_change()
{
    using namespace std::chrono;
    while (true)
    {
        auto new_zone = current_zone();
        if (zone != new_zone)
        {
            std::lock_guard lock(zone_mutex);
            zone = new_zone;
            zone_cv.notify_all();
        }
        // Adjust this to however responsive you want to be
        //   to changes in time zone
        std::this_thread::sleep_for(1min);
    }
};

Все, что делает эта функция, — это время от времени просыпается и смотрит, изменилось ли значение возврата от current_zone(). Если да, то он изменяет глобальную переменную zone и уведомляет другие потоки с помощью zone_cvcondition_variable. У меня проверка раз в минуту. Вы можете установить частоту опроса на любую, подходящую для вашего приложения.

execute_at_time_of_day теперь изменится на:

template <class F>
void
execute_at_time_of_day(std::chrono::system_clock::duration local_tod, F f)
{
    using namespace std::chrono;

    std::unique_lock lock{zone_mutex};
    auto now_utc = system_clock::now();
    auto time_to_execute = zone->to_local(now_utc);
    time_to_execute = floor<days>(time_to_execute) + local_tod;
    if (zone->to_sys(time_to_execute) < now_utc)
        time_to_execute += days{1};
    while (true)
    {
        if (zone_cv.wait_until(lock,
            zone->to_sys(time_to_execute)) == std::cv_status::timeout ||
            zone->to_sys(time_to_execute) < system_clock::now())
        {
            lock.unlock();
            f();
            time_to_execute += days{1};
            lock.lock();
        }
    }
}

Примечания:

Эта функция должна быть zone_mutex заблокирована каждый раз, когда она читает (использует) глобальную zone, то есть все время, кроме времени, когда она находится в режиме ожидания или выполнения f.

Инициализация time_to_execute работает как и раньше. Но метод сна изменился. Во время сна мы хотим иметь возможность проснуться по двум причинам:

  1. Потому что день прошел, или
  2. Потому что изменился местный часовой пояс.

Для этого мы «спим» на condition_variable с wait_until. Если время ожидания истекло, значит прошли сутки, в противном случае нас уведомили о смене часового пояса. Кроме того, природа условных переменных такова, что потоки могут неожиданно пробуждаться. Таким образом, код должен быть устойчив к возможности того, что он просыпается, а ни дня не прошло, ни часовой пояс не изменился.

Если часовой пояс изменился, желаемое местное время выполнения может все еще быть в будущем или в прошлом. Только если zone_cv сообщает об истечении времени ожидания или если желаемое время выполнения уже в прошлом, мы хотим выполнить функцию и вычислить новое время выполнения. В противном случае желаемое время выполнения находится в будущем, и у нас либо было ложное пробуждение, либо изменение часового пояса, из-за которого желаемое время выполнения было перенесено в будущее, поэтому мы ничего не делаем, кроме как снова заснуть.

В этом решении main необходимо изменить, чтобы запустить поток check_for_zone_change:

std::thread{check_for_zone_change}.detach();

Время суток: местное/Мобильное устройство/Требуется длительное время безотказной работы.

Наконец, если приложение должно работать в течение нескольких месяцев и во время работы обновляться до последней базы данных часовых поясов IANA, вам не повезло в ОС Windows и Apple. Но в Linux это можно сделать с помощью gcc:

См. документацию для:

const std::chrono::tzdb& reload_tzdb();
std::string remote_version();

Вызов reload_tzdb() обновит вашу базу данных часовых поясов, если она доступна. Последующие вызовы current_zone() и locate_zone() будут ссылаться на эту новую базу данных. Прежде чем вызвать reload_tzdb(), вы можете сравнить remote_version() с get_tzdb().version, чтобы узнать, изменит ли повторная загрузка базы данных номер версии базы данных.

Можно создать еще одну ветку для опроса remote_version() и звонка reload_tzdb() при обнаружении изменения версии.

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