Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

QTimer can only be used with threads started with QThread



  • Hello,

    I'm trying to implement a class into my application that i got from a colleague.
    The class is supposed to create a log file and write into that log file. The class works fine in his application and an application from another colleague but i get the warning "QObject::startTimer: QTimer can only be used with threads started with QThread".
    Now we both compared classes and implementations in the application but there is no differences in using the class.

    log.h:

    #ifndef LOG_H
    #define LOG_H
    
    #include <QCoreApplication>
    #include <QFile>
    #include <QTextStream>
    #include <QDateTime>
    #include <QTimer>
    #include <QQueue>
    #include <QThread>
    #include <QDebug>
    #include <QMutex>
    
    class Log : public QThread
    {
        Q_OBJECT
    
    public:
        void add(const QString type, const QString str);
        bool saving_lock = false;
        static Log& instance()
        {
             static Log theInstance;
             return theInstance;
        }
        bool mutex;
    private:
        Log();
        QQueue<QString> printqueue;
        QTextStream * text_stream;
        QFile * text_file;
        QTimer timer;
    
    private slots:
        void WriteNext();
    };
    
    #endif // LOG_H
    

    log.cpp

    #include "log.h"
    
    Log::Log()
    {
        mutex = false;
    
        timer.setInterval(10);
        QObject::connect(&timer, SIGNAL(timeout()), this, SLOT(WriteNext()));
        QString filename = "/home/pi/DSS/logfile.axt";
        timer.start(10);
        text_file = new QFile(filename);
        if (!text_file->open(QIODevice::ReadWrite | QIODevice::Append | QIODevice::Text)) {
            return;
        }
        if(text_file->size() > (1000000 * 256))
        {
            text_file->resize(0);
        }
        text_file->seek(text_file->size());
        text_stream = new QTextStream(text_file);
        add("","________________________________________________");
        add("","");
        add("INFO", "Started logging");
        add("","________________________________________________");
    }
    
    void Log::add(const QString type, const QString str)
    {
        while(mutex){qDebug()<<"lockedadd";};
        mutex = true;
        QString toAdd = ("[" + QDateTime::currentDateTime().toString() +"."+ QString::number(QDateTime::currentDateTime().time().msec()) + "] " + type.toUpper() + " " + str);
        printqueue.enqueue(toAdd);
        mutex = false;
    }
    
    void Log::WriteNext()
    {
        while(mutex){qDebug()<<"lockedwrite";};
        mutex = true;
        int lockedDequeues = printqueue.count();
        for(int x = 0; x < lockedDequeues; x++)
        {
            QString toWrite = printqueue.dequeue();
            *text_stream << toWrite << endl;
        }
        mutex = false;
    }
    

    Is there something I'm forgetting and just not seeing?
    If there is more you need just ask.

    Thank you!


  • Moderators

    I'm surprised as you are, as you do not multithread at all an everything should live and execute in the main (Gui) thread.

    do you call instance() in an actual different thread ? that could cause the issue you describe



  • Only time i use instance is when i want to add something to the log.

    Can it be that there is another timerEvent running in another class?

    void Watchdog::timerEvent(QTimerEvent *event){
        qDebug() << "Timer ID:" << event->timerId();
        if (count >= TicksTillSave)
        {
            SaveToDB = true;
            count = 0;
        }
        else
        {
            count++;
        }
        PrepareWatchdogData();
    }
    


  • @iJeanPaul Why do you subclassing QThread and not QObject? Subclassing QThread don't made sense to me in your context and it is not a good practice in general, it is better to create a worker class and move it to the dedicated thread.
    I think you will also got some multi-threading issues with your variable mutex, bool is not an atomic type! Why not using QMutex?

    It do not look like a very good implementation... Sorry to be so hard :(



  • So I've changed a few things with a colleague and it almost works now.
    So in the header file underneath Q_OBJECT I've added:

        void run() {
            timer.start(10);
        }
    

    en to the cpp file I've added:

    start()
    

    to the beginning of the file.

    It now only writes away "INFO Started logging" and some other data from another class.

    So at first i thought there was something wrong in the the class because it writes away from one but not the other.
    So i added a breakpoint to the WriteNext() function at "*text_stream << toWrite << endl;" and looked at the queue and saw that the line from the other class is added to the queue and also deleted after it is supposed to be written away. But when looking at the log file it did not write away that line and it has not written away any lines after that...

    So the timer works now because the program keeps coming back to WriteNext() but after about 15 lines or so it just stops writing away the data into the log file.


  • Moderators

    @iJeanPaul
    here:

    #ifndef LOG_H
    #define LOG_H
    
    #include <QCoreApplication>
    #include <QFile>
    #include <QTextStream>
    #include <QDateTime>
    #include <QQueue>
    #include <QThread>
    #include <QDebug>
    #include <QMutex>
    
    class QTimer;
    class Log : public QThread
    {
        Q_OBJECT
    
    public:
        void add(const QString type, const QString str);
        static Log& instance()
        {
             static Log theInstance;
             return theInstance;
        }
        QMutex mutex;
    
    protected:
        virtual void run() override;
    
    private:
        Log();
    
        void init();
        QQueue<QString> printqueue;
        QTextStream * text_stream;
        QFile * text_file;
        QTimer *timer;
    
    private slots:
        void WriteNext();
    };
    
    #endif // LOG_H
    
    
    #include "log.h"
    
    #include <QTimer>
    
    Log::Log() :QThread(nullptr)
    {
        QMetaObject::invokeMethod(this, [=]()->void{
            start();
        },Qt::QueuedConnection);
    }
    
    void Log::init()
    {
        qDebug() <<Q_FUNC_INFO;
        timer = new QTimer();
        timer->setInterval(10);
        connect(timer, &QTimer::timeout, this, &Log::WriteNext);
    
        timer->start();
    
        QString filename = "/home/pi/DSS/logfile.axt";
        text_file = new QFile(filename);
        if (!text_file->open(QIODevice::ReadWrite | QIODevice::Append | QIODevice::Text)) {
            return;
        }
        if(text_file->size() > (1000000 * 256))
        {
            text_file->resize(0);
        }
        text_file->seek(text_file->size());
        text_stream = new QTextStream(text_file);
        add("","________________________________________________");
        add("","");
        add("INFO", "Started logging");
        add("","________________________________________________");
    }
    
    void Log::add(const QString type, const QString str)
    {
        mutex.tryLock(-1);
        QString toAdd = ("[" + QDateTime::currentDateTime().toString() +"."+ QString::number(QDateTime::currentDateTime().time().msec()) + "] " + type.toUpper() + " " + str);
        printqueue.enqueue(toAdd);
        mutex.unlock();
    }
    
    void Log::run()
    {
        init();
    
        exec();
    }
    
    void Log::WriteNext()
    {
        qDebug() << Q_FUNC_INFO;
        mutex.tryLock(-1);
        int lockedDequeues = printqueue.count();
        for(int x = 0; x < lockedDequeues; x++)
        {
            QString toWrite = printqueue.dequeue();
            *text_stream << toWrite << endl;
        }
        mutex.unlock();
    }
    
    


  • @J-Hilk Thank you so much for this! Only thing is that the QMetaObject::invokeMethod gives me an error saying:
    "no matching function for call to 'invokeMethod'". While the list of options after QMetaObject:: gives me invokeMethod.

    Maybe it's because I'm working in QT4 (the Raspberry PI was set up for me and that is what i have to work with and everyone here working with QT works with QT4). I've already changed the connect string so only thing that is not working now is the above.

    Searched on Google but couldn't find an answer straight away. Going to search some more but if you maybe have a solution for that too at the moment that'd be great.

    Thank you!


  • Moderators

    @iJeanPaul
    change it to

    QMetaObject::invokeMethod(this, "start", Qt::QueuedConnection);
    

    or remove it from the constructor and change your instance() function to this:

    static Log& instance()
        {
             static Log theInstance;
             if(!theInstance.isRunning())
                 theInstance.start();
             return theInstance;
        }
    


  • @J-Hilk
    So it runs at it keeps calling the WriteNext() function which is great.

    Now the problem is that it is not writing everything in the queue to the logfile.
    When i debug I immediately see 18 items in the queue including "program started" which is located in the main.cpp and the "started logging" line.
    But when the list is clear it has only written 10 items to the logging file.
    It does go by them one by one and deletes them one by one.


  • Moderators

    @iJeanPaul with what option did you go? I would suggest the 2nd one, the QQuedConnection may mess things up

    this is my log file:

    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 0
    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 1
    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 2
    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 3
    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 4
    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 5
    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 6
    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 7
    [Do. Okt. 31 10:17:04 2019.877] SOMETYPE 8
    [Do. Okt. 31 10:17:04 2019.878] SOMETYPE 9
    [Do. Okt. 31 10:17:04 2019.878] SOMETYPE 10
    [Do. Okt. 31 10:17:04 2019.878] SOMETYPE 11
    [Do. Okt. 31 10:17:04 2019.878] SOMETYPE 12
    [Do. Okt. 31 10:17:04 2019.878] SOMETYPE 13
    [Do. Okt. 31 10:17:04 2019.878] SOMETYPE 14
    [Do. Okt. 31 10:17:04 2019.878]  ________________________________________________
    [Do. Okt. 31 10:17:04 2019.879] SOMETYPE 15
    [Do. Okt. 31 10:17:04 2019.879]  
    [Do. Okt. 31 10:17:04 2019.879] SOMETYPE 16
    [Do. Okt. 31 10:17:04 2019.879] INFO Started logging
    [Do. Okt. 31 10:17:04 2019.879] SOMETYPE 17
    [Do. Okt. 31 10:17:04 2019.879]  ________________________________________________
    [Do. Okt. 31 10:17:04 2019.879] SOMETYPE 18
    [Do. Okt. 31 10:17:04 2019.879] SOMETYPE 19
    

    and my main looks like this:

    int main(int argc, char *argv[])
    {
    
        QApplication app(argc, argv);
    
        auto &l =Log::instance();
    
        for(int i =  0; i < 20; i++){
        l.add(QString("SomeType"), QString::number(i));
        }
    
        return  app.exec();
    }
    

    as you can see its mixed up, but that's to be expected, the 2 threads are running in parallel after all.
    And that means init is executed in the 2nd thread -> it's not necessarily the first entry!
    but everything is added to the file



  • @J-Hilk
    I tried this and commented the logging lines on the other classes and it works like it should so first of all thank you for that.
    It probably had to do with a line in the other class that it just stops which is:

    Log::instance().add("INFO", "SaveToDB: " + QString(SaveToDB));
    

    Where SaveToDB is supposed to be "false".
    I see that in the logging it just says "INFO SaveToDB:" and that's it. It just ends there.
    When i comment that line out it just works fine.

    So thank you very much for your sollution!!!

    EDIT: Oh and i first went with your first option but after you suggested the 2nd one I'm using the 2nd one. :P
    Also the line above isn't neccesary. There is a line underneath that already takes care of it and the colleague that made this class forgot to delete this line...


  • Moderators

    @iJeanPaul
    glad to be of help,
    don't forget to use the topic tools to set the topic to solved 😉


Log in to reply