QBasicTimer::start: Timers cannot be started from another thread issue
-
Hi! The code works but I get the issue -
QBasicTimer::start: Timers cannot be started from another thread
. It occurs when I useQEventLoop
in the thread.Code:
void Test::checkDBUpdate() { QEventLoop *checkDBEventLoop; QFile *dbFile = new QFile(QString(DB_DIR) + QString(DB_FILE)); dbFile->open(QIODevice::ReadOnly); QTextStream dbTextStream(dbFile); QString localDBFile = dbTextStream.readAll(); QString ftpDBFile = ""; checkDBEventLoop = new QEventLoop(this); QNetworkReply *checkDBReply = netManager->get(QNetworkRequest(dbFtpUrl())); connect(checkDBReply, &QNetworkReply::finished, checkDBEventLoop, &QEventLoop::quit); checkDBEventLoop->exec(); if (checkDBReply->bytesAvailable()) { ftpDBFile = checkDBReply->readAll(); qDebug() << localDBFile.length(); qDebug() << ftpDBFile.length(); if (localDBFile.length() != ftpDBFile.length()) { emit dbUpdateAvailable(); } else { emit dbLatest(); } } dbFile->close(); delete dbFile; delete checkDBReply; delete checkDBEventLoop; }
I don't have any timers set. How to fix the issue? Thanks.
-
@Cobra91151 Why do you need an event loop? Why not just use Qt in the way it was designed: asynchronously?
Do you start any threads?
-
@Cobra91151 Can you show where and how you start the thread?
-
Ok.
Code:
void AppSettings::checkDatabaseUpdate() { QThread *dbUpdateThread = new QThread(); Test *dbUpdate = new Test(); dbUpdate->moveToThread(dbUpdateThread); connect(dbUpdateThread, &QThread::started, dbUpdate, &Test::checkDBUpdate); connect(dbUpdate, &Test::dbUpdateAvailable, this, &AppSettings::setDBUpdate); connect(this, &AppSettings::dbUpdateConfirmed, dbUpdate, &Test::updateDatabase); connect(dbUpdate, &Test::updateDBCompleted, this, &AppSettings::setDBUpdated); connect(dbUpdate, &Test::dbLatest, this, &AppSettings::setDBLatest); connect(this, &AppSettings::dbUpdateCanceled, dbUpdate, &Test::setUpdateCanceled); connect(dbUpdate, &Test::updateDBFinished, dbUpdateThread, &QThread::quit); connect(dbUpdate, &Test::updateDBFinished, dbUpdate, &Test::deleteLater); connect(dbUpdateThread, &QThread::finished, dbUpdateThread, &QThread::deleteLater); dbUpdateThread->start(); }
-
@Cobra91151 Is netManager initialized in Test constructor? If so try to do it in Test::checkDBUpdate().
I do not see any reason to start a thread in AppSettings::checkDatabaseUpdate() which executes a blocking method. Just make Test::checkDBUpdate() non blocking. You really over complicate things. -
Yes,
netManager
(QNetworkAccessManager
) is initialized in theTest
constructor. I have initialized it in the constructor to access not only for theTest::checkDBUpdate()
, but also for the other features which requireQNetworkAccessManager
. So I can't initialized it twice or inTest::checkDBUpdate()
.AppSettings
class is a GUI window and I connect it there to get the result of the work. It's very big and complicated application.What do you mean by blocking method?
-
@Cobra91151 I mean void Test::checkDBUpdate() is blocking (not returning) until it is finished (that's why you use event loop there, right?). So, you start a thread to execute a blocking method to not block main thread instead of simply making void Test::checkDBUpdate() non blocking. You introduce a complex and error prone work-around (thread) for something you could simply change. Also: why do you use new so often (for example there is no need to allocate dbFile on the heap)? Why not just allocate memory on the stack?
Back to this timer issue: you create netManager in constructor and then later move Test to another thread, I guess netManager is still in the main thread.
-
@Cobra91151 Here a non-blocking solution without any threads (untested!):
void Test::checkDBUpdate() { QFile dbFile(QString(DB_DIR) + QString(DB_FILE)); dbFile.open(QIODevice::ReadOnly); QTextStream dbTextStream(dbFile); QString localDBFile = dbTextStream.readAll(); QNetworkReply *checkDBReply = netManager->get(QNetworkRequest(dbFtpUrl())); connect(checkDBReply, &QNetworkReply::finished, [checkDBReply, localDBFile]() { if (checkDBReply->bytesAvailable()) { QString ftpDBFile = checkDBReply->readAll(); qDebug() << ftpDBFile.length(); if (localDBFile.length() != ftpDBFile.length()) { emit dbUpdateAvailable(); } else { emit dbLatest(); } } checkDBReply->deleteLater(); }); } void AppSettings::checkDatabaseUpdate() { Test *dbUpdate = new Test(); connect(dbUpdate, &Test::dbUpdateAvailable, this, &AppSettings::setDBUpdate); connect(this, &AppSettings::dbUpdateConfirmed, dbUpdate, &Test::updateDatabase); connect(dbUpdate, &Test::updateDBCompleted, this, &AppSettings::setDBUpdated); connect(dbUpdate, &Test::dbLatest, this, &AppSettings::setDBLatest); connect(this, &AppSettings::dbUpdateCanceled, dbUpdate, &Test::setUpdateCanceled); connect(dbUpdate, &Test::updateDBFinished, dbUpdateThread, &QThread::quit); connect(dbUpdate, &Test::updateDBFinished, dbUpdate, &Test::deleteLater); dbUpdate->checkDBUpdate(); }
-
Thanks for the code but I get errors:
error: C4573: the usage of 'CheckUpdates::dbUpdateAvailable' requires the compiler to capture 'this' but the current default capture mode does not allow it
When changing lambda to
connect(checkDBReply, &QNetworkReply::finished, [checkDBReply, this]() {
I get error:error: C3493: 'localDBFile' cannot be implicitly captured because no default capture mode has been specified
-
@Cobra91151 said in QBasicTimer::start: Timers cannot be started from another thread issue:
connect(checkDBReply, &QNetworkReply::finished, checkDBReply, this {
Why did you remove localDBFile?
connect(checkDBReply, &QNetworkReply::finished, [checkDBReply, localDBFile, this]() {