Блокировка до появления одного или нескольких сигналов

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

Я не рад, что вам нужно явно указать std::tuple на сайте вызова (см. использование ниже), но я не видел способа, который по-прежнему позволял бы использовать завершающий необязательный аргумент тайм-аута. Также я использую его только в фоновых потоках, у которых еще нет цикла событий, возможно, это может испортить основной цикл событий, если он используется в основном потоке.

Ссылка на обозреватель компилятора

Реализация

/*!
 * brief waitForSignals
 * waits until any of the given signals is emitted by the given sender or the timeout is reached (if given)
 * param src
 * source object to monitor
 * param sigs
 * the signals to listen for
 * param timeoutMs
 * maximum time to listen for the signal before returning. if this is negative, no timeout is used
 * return
 * true if one of the signals got emitted. false if the operation timed out
 */
template< typename Sender, typename ...Signal,
    std::enable_if_t<
        std::is_base_of_v<QObject, Sender> &&
        (sizeof...(Signal) > 0) &&
        (std::is_member_function_pointer_v<Signal> && ...) >* = nullptr >
bool waitForSignals(Sender *src, std::tuple<Signal...> sigs , int timeoutMs = -1)
{
    QEventLoop loop;
    std::apply([&loop, src](auto... s){
        (QObject::connect(src, s, &loop, &QEventLoop::quit), ...);
    }, sigs);

    QTimer timer;
    bool aborted = false;
    if(timeoutMs >= 0)
    {
        QObject::connect(&timer, &QTimer::timeout, [&](){ aborted = true; });
        QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
        timer.setSingleShot(true);
        timer.start(timeoutMs);
    }

    loop.exec();
    return timeoutMs < 0 || !aborted;
}
/*!
 * brief waitForSignal
 * like a waitForSignals but only waits for the one signal insead of a list
 * sa waitForSignals
 */
template< typename Sender, typename Signal>
bool waitForSignal(Sender *src, Signal sig , int timeoutMs = -1)
{
    return waitForSignals(src, std::tuple{sig}, timeoutMs);
}

использование

MyObject obj;
waitForSignal(&obj, &MyObject::mySignalOne);
waitForSignal(&obj, &MyObject::mySignalOne, 100);
waitForSignals(&obj, std::tuple{&MyObject::mySignalOne, &MyObject::mySignalTwo});
waitForSignals(&obj, std::tuple{&MyObject::mySignalOne, &MyObject::mySignalTwo}, 100);

1 ответ
1

Избегая std::tuple

Я не рад, что вам нужно явно указать std::tuple на сайте вызова (см. использование ниже), но я не видел способа, который по-прежнему позволял бы использовать завершающий необязательный аргумент тайм-аута.

Есть способ преобразовать функцию в class который выполняет всю работу в своем конструкторе, а затем предоставляет руководство по дедукции, как показано на этот ответ StackOverflow.

Ты не первый чтобы столкнуться с этой проблемой, и она может быть исправлена ​​в C ++ 23, возможно, если компилятор автоматически определит правильную вещь, или, может быть, путем предоставления руководств по дедукции для бесплатных функций.

Не нужно подключать к таймеру два действия

Подключать два действия к таймеру не нужно. У вас может быть лямбда для выхода из цикла событий, но еще лучше, чтобы таймер только выходил из цикла событий, а не устанавливал переменную aborted, но вместо этого просто проверьте timer.isActive() после того, как цикл закончился.

  • Это хитрый трюк с руководством по дедукции

    — Дэйв

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *