Я написал эту небольшую вспомогательную функцию, которая будет блокировать текущий поток до тех пор, пока какой-либо из заданного списка сигналов не будет испущен объектом или пока не будет достигнут необязательный тайм-аут. Его можно использовать для синхронизации сетевого общения или чего-то подобного.
Я не рад, что вам нужно явно указать 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 ответ
Избегая std::tuple
Я не рад, что вам нужно явно указать
std::tuple
на сайте вызова (см. использование ниже), но я не видел способа, который по-прежнему позволял бы использовать завершающий необязательный аргумент тайм-аута.
Есть способ преобразовать функцию в class
который выполняет всю работу в своем конструкторе, а затем предоставляет руководство по дедукции, как показано на этот ответ StackOverflow.
Ты не первый чтобы столкнуться с этой проблемой, и она может быть исправлена в C ++ 23, возможно, если компилятор автоматически определит правильную вещь, или, может быть, путем предоставления руководств по дедукции для бесплатных функций.
Не нужно подключать к таймеру два действия
Подключать два действия к таймеру не нужно. У вас может быть лямбда для выхода из цикла событий, но еще лучше, чтобы таймер только выходил из цикла событий, а не устанавливал переменную aborted
, но вместо этого просто проверьте timer.isActive()
после того, как цикл закончился.
Это хитрый трюк с руководством по дедукции
— Дэйв