Прежде всего, вы не должны определять слоты в своем подклассе QThread
и вызывать их изнутри run()
- слоты будут выполняться (выполняя перекрестный вызов слотов) в контексте потока, который владеет вашим UpdateThread
экземпляром (тем же, который его создал, если вы не вызвали moveToThread()
для него), а не в контексте потока, представленного UpdateThread
. Запомните эту мнемонику:
In run()
, QThread::thread() != this
Вместо этого определите слоты в подклассе QObject, который вы создаете внутри run()
.
Хорошо, с этим из пути, давайте посмотрим на таймер. Документация QTimer
содержит следующее:
В многопоточных приложениях вы можете использовать QTimer
в любом потоке, в котором есть цикл обработки событий.
Чтобы запустить цикл обработки событий из потока без графического интерфейса, используйте QThread::exec()
. Qt использует таймер
аффинность потока , чтобы определить, какой поток будет излучать сигнал timeout()
.
Из-за этого вы должны запускать и останавливать таймер в его потоке; это невозможно
запустить таймер из другого потока.
(выделено мной) Обратите особое внимание на последнее предложение.
Решение состоит в том, чтобы сделать вызов между потоками QTimer::start()
и QTimer::stop()
. Возможно, вы знаете о сквозных соединениях сигнал / слот. При этом используется тот же базовый механизм, который представлен в QMetaObject::invokeMethod()
:
class UpdateThread : public QThread {
Q_OBJECT
private:
QObject * m_timer; // just a QObject* so we're not tempted
// to call functions on it
QMutext m_mutex; // protects 'm_timer'
public:
explicit UpdateThread( QObject * parent=0 )
: QThread( parent ), m_timer( 0 ) {}
// ...
private:
/* reimpl */ void run() {
QTimer timer;
// ...'timer' setup code goes here...
{
const QMutexLocker locker( &m_mutex );
m_timer = &timer; // publish 'timer' through 'm_timer'
}
// main code of run()
exec(); // start event loop so we get timer's timeout()s
// and we can receive cross-thread method calls
{
const QMutexLocker locker( &m_mutex );
m_timer = 0; // un-publish before we delete `timer`
}
}
public Q_SLOTS:
void startTimer() {
const QMutexLocker locker( &m_mutex );
if ( !m_timer ) return;
// perform cross-thread method call:
QMetaObject::invokeMethod( m_timer, "start", Qt::QueuedConnection );
}
void stopTimer() {
const QMutexLocker locker( &m_mutex );
if ( !m_timer ) return;
// perform cross-thread method call:
QMetaObject::invokeMethod( m_timer, "stop", Qt::QueuedConnection );
}
};
Теперь вы можете запускать / останавливать таймер из потока графического интерфейса. Но вы также спрашивали об альтернативах.
Перемещение tick()
кода в run()
, вызов UpdateThread::start()
каждые t
миллисекунд.
Это неоптимально, поскольку оно будет создавать и уничтожать потоки каждые t
мс. Создание потока - все еще дорогая операция. Кроме того, если к следующему звонку start()
не будет выполнено UpdateThread::run()
, вы потеряете отметки таймера.
UpdateThread
, как указано выше.
Это не так уж плохо, но это не идиоматическая многопоточность, я бы сказал. Это хорошее решение, если таймер срабатывает так часто, что это само по себе может как-то замедлить поток GUI, хотя вы можете потерять тики таймера и в этом случае.
QThreadPool
Мой любимый. Переместите код, который выполняет tick()
, в реализацию QRunnable::run()
и ставьте новый исполняемый файл в очередь потоков при каждом срабатывании таймера. В этом случае таймер наиболее естественно будет жить в потоке графического интерфейса, избегая необходимости перекрестных вызовов методов, как описано выше. Если сам поток GUI не перегружен, вы не пропустите ни одного такта таймера. Вы также получаете бесплатное масштабирование до количества ядер в системе (если вы этого не хотите, не используйте QThreadPool::globalInstance()
, но создайте свой собственный экземпляр и вызовите setMaxThreadCount(1)
).