There is some confusion about the correct way to farm jobs out to QThreads so I was hoping somebody would chip in with a bit more detail. You can either do it by subclassing QThread and doing the work in there, or by creating a QObject which does the work and moving it to a vanilla QThread (both ways are shown here).
We've plumped for the QObject + vanilla QThread method, which has taken all of an hour and a half to program, so I guess it's not too bad even for developers who don't normally use QThread.
Our working solution
//MainGuiProgram.cpp
MainGuiProgram::MainGuiProgram(QWidget *parent)
: QMainWindow(parent)
{
ui.setupUi(this);
RefreshDataWorker *worker = new RefreshDataWorker;//our worker class
worker->moveToThread(&workerThread);//MainGuiProgram has a private member QThread workerThread
connect(&workerThread, &QThread::finished, worker, &QObject::deleteLater);
connect(this, &CrawlerManager::triggerWorkerToWork, worker, &RefreshDataWorker::doWork);//MainGuiProgram has a signal which tells the worker class to start
connect(worker, &RefreshDataWorker::resultReady, this, &CrawlerManager::updateResults);//Gets results back from the worker when it's finished
workerThread.start();
}
void MainGuiProgram::connectToDatabase()
{
//connect to database here, this connection is used by the GUI
QTimer *t = new QTimer(this);
t->setInterval(1000);
connect(t, SIGNAL(timeout()), this, SLOT(refreshGuiData()));
t->start();
}
void MainGuiProgram::refreshGuiData()
{
QDateTime nextRefreshTime = QDateTime::fromString("20/07/2021 09:00", "dd/MM/yyyy HH:mm"); //hard coded for example's sake
if (QDateTime::currentDateTimeUtc().secsTo(expiry) <= 0 && updateHasHappened == false)
{
ui.statusLabel->setText("Update in progress!");
ui.statusLabel->repaint();
emit triggerWorkerToWork();//The signal which tells the worker to do its work
updateHasHappened = true;
}
}
void MainGuiProgram::updateResults(const QString &result)
{
ui.statusLabel->setText(result);
}
//RefreshDataWorker.cpp
void RefreshDataWorker::doWork()
{
//connect to database again here, this connection is used by the worker
//and thrown away when the refresh is finished
ourDatabaseClass.doStuffWithDatabase();
QString result = "Done!";
emit resultReady(result);//a signal which the Main GUI receives when the result is done
}
Passing the result back using a signal allows the Main GUI thread to update the user interface, thus avoiding @jsulm 's warning about not touching the GUI from other threads.
One important point not shown in this code is that the database connections (MySQL) needed to be named, so that the worker can identify and delete its own connection without mucking up the connection used by the main thread.
Solution to our original questions
What is the best practice in terms of Qt classes to execute timed events in a separate thread? Create a QObject worker, farm it out to a basic QThread instance, and tell it when to work using signals triggered from a QTimer running in the program's main thread.
When trying to do something at a specific time of day in Qt, must one always use QTimer with an interval? Yes, but you can tell the timer to sleep for periods of 1 second at a time in order to not have to constantly poll the system time to know when to start working.
If we want different elements of the GUI to update at different times of the day, should we set a separate QTimer for each of them? No. You can use one QTimer, which wakes up every second, and then each time it wakes up inspect the current time to know which jobs to kick off
If nobody posts any other comments with respect to the above I'll mark it as the solution later today. Happy days