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 use QEventLoop 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.


  • Moderators

    @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?



  • @jsulm

    Yes. The work starts in the thread with connected signals and slots. I use QEventLoop to maintain all check work in the checkDBUpdate() slot.


  • Moderators

    @Cobra91151 Can you show where and how you start the thread?



  • @jsulm

    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();
    }
    

  • Moderators

    @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.



  • @jsulm

    Yes, netManager (QNetworkAccessManager) is initialized in the Test constructor. I have initialized it in the constructor to access not only for the Test::checkDBUpdate(), but also for the other features which require QNetworkAccessManager. So I can't initialized it twice or in Test::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?


  • Moderators

    @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.


  • Moderators

    @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();
    }
    


  • @jsulm

    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
    

  • Moderators

    @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]() {
    


  • @jsulm

    Yes, you right. I missed it. Now it works. Thank you.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.