Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
168 views
in Technique[技术] by (71.8m points)

c++ - How to create qDebug signal from another thread to the Qt5 GUI thread

I am trying to display log messages from a work thread in a GUI. I am trying to follow

redirect qDebug to QTextEdit

It started to work fine, but I am stuck, how to program

QObject::connect(otherThread, SIGNAL(debug(QString)),
                 s_textEdit, SLOT(append(QString)), Qt::QueuedConnection);

The principle I see, that one signal in the thread shall be connected to a slot in the GUI thread; but how to trigger that signal? Also, I make some logging with QDebug, but also some output to std::cerr. Can I mix these outputs? (I mean, probably I shall make another signal, but shall I flush the messages, or I can use one instance of Qt::QueuedConnection)

Another question about using QMutex. Basically, I am just reading the values set by the other thread, and starting/stopping the tread. Do I need to use QMutex in such simple case? (I mean I know why to use a mutex; my question is about that when using Qt, the internal mechanisms of GUI handling and thread handling may make it a need)

My trial thread is actually a demo code

void SimulatorThread::run()
{
    for(int i = 0; i <= 10; i++)
    {
        QMutex mutex;
        // prevent other threads from changing the "Stop" value
        mutex.lock();
        if(this->Stop) break;
        mutex.unlock();

        emit debug("my text");

        // slowdown the count change, msec
        this->msleep(500);
    }
}

The connect I make in the constructor of the QMainWindow, before resize().

    createMenus();
    ...
    mThread = new SimulatorThread(this);
    QObject::connect(mThread, SIGNAL(debug(QString)),
                     s_textEdit, SLOT(append(QString)), Qt::QueuedConnection); 

I am using

Qt 5.9.5
g++ (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0

Actually, I was lazy, and I inserted the startup common in the 'About' box.

void SimulatorWindow::on_actionAbout_triggered() {
    AboutWidget about;
    about.exec();
    mThread->run();
    qInfo( "Thread started up
");
}

The thread returns when the loop in the thread is over. I do not receive the qInfo() message, and if I put a breakpoint after the line of qInfo(), I receive a message in the Qt Creator application message.

RTTI symbol not found for class 'QObject'

If I have the breakpoint, I do not receive my messages in the GUI window. If I run it without breakpoint, I do, but only when the loop is over, and that time there is no 'RTTI symbol not found'.

Something must be wrong with the synchronization. Even, with breakpoint, it also freezes my system.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

but how to trigger that signal?

Declare the signal in your QObject-inherited class' interface. Insert Q_OBJECT macro in the class declaration. Read Singals and Slots in Qt.

Example. Subclass QThread in your case (QThread inherits QObject). Read Threading Basics in Qt and QThread docs.

Header

#include <QThread>

class OtherThread : public QThread
{
    Q_OBJECT

public:
    OtherThread(QObject *parent);
    ~OtherThread();

signals:
    void debug(QString);

    // You have to override run(). Don't try to call it anywhere.
    // There is start() method to start a thread
protected:
    void run() override;
};

emit the signal from the place you need:
Source file

#include "OtherThread.h"

OtherThread::OtherThread(QObject *parent)
    : QThread(parent)
{ }

OtherThread::~OtherThread()
{
    if(isRunning())
    {
        // Stop our loop
        requestInterruption();

        // Waits until return from run()
        wait();
    }
}

void OtherThread::run()
{
    int it = 0;

    while(!isInterruptionRequested())
    {
        // the line below will enqueue some call to the GUI thread
        // no event loop in the sender thread is needed
        emit debug(QString::number(it++));    

        msleep(500);
    }
}

GUI class header file

#include <QtWidgets/QMainWindow>

class MainWin : public QMainWindow
{
    Q_OBJECT

public:
    MainWin(QWidget *parent = Q_NULLPTR);
};

GUI class source file

#include "MainWin.h"
#include "OtherThread.h"

#include <QTextEdit>
#include <QTimer>

MainWin::MainWin(QWidget *parent)
    : QMainWindow(parent)
{    
    auto textEdit = new QTextEdit(this);
    setCentralWidget(textEdit);

    auto otherThread = new OtherThread(this);

    /* 
    No need to specify the connection type. 
    Qt::AutoConnection will be used by default.
    In case of an automatic connection, Qt looks at the thread that invoked the signal 
    and compares it with the thread the receiver is living in to determine 
    which connection type it has to use. 
    */
    connect(otherThread, &OtherThread::debug,
        textEdit, &QTextEdit::append);

    // Attention: call start(), not run()!
    otherThread->start(); 

    // For example you want to stop the thread after 5 seconds
    QTimer::singleShot(5000, [=]() { otherThread->requestInterruption(); });
}

Do the same using High-Level QtConcurrent API:

#include "MainWin.h"

#include <QtConcurrent/QtConcurrent>
#include <QThread> // for msleep

#include <atomic>
#include <QTextEdit>
#include <QTimer>

// Thread-safe flag to stop the thread. No mutex protection is needed 
std::atomic<bool> gStop = false;

MainWin::MainWin(QWidget *parent)
    : QMainWindow(parent)
{    
    auto textEdit = new QTextEdit(this);
    setCentralWidget(textEdit);

    // Run the code in another thread using High-Level QtConcurrent API
    QtConcurrent::run([=]()
    {
        int it = 0;

        while(!gStop)
        {
            QString text = QString::number(it++);

            // No need to explicitly specify Qt::QueuedConnection, 
            // Qt::AutoConnection will be used
            QMetaObject::invokeMethod(textEdit, "append",
                Q_ARG(QString, text));

            QThread::msleep(500);
        }
    });

    // Timer to stop the thread after 5 seconds
    QTimer::singleShot(5000, [=]() { gStop = true; });
}

MainWin::~MainWin()
{
    // Stop the loop if we exit the program earlier than after 5 seconds,
    // to avoid undefined behaviour in that case 
    gStop = true;
}

Please also note that Qt provides unified place to control all the debug, warning, error and other types of messages:

Qt Debuggin Techniques for c++
qInstallMessageHandler()

Now, you can install the event handler once and then all messages will go to one place where you can output them where necessary, not using custom connections.

Please note that Qt provides several global macros for writing out warning and debug text:

qDebug() is used for writing custom debug output.
qInfo() is used for informational messages.
qWarning() is used to report warnings and recoverable errors in your application.
qCritical() is used for writing critical error messages and reporting system errors.
qFatal() is used for writing fatal error messages shortly before exiting.

.

Also, I make some logging with QDebug, but also some output to std::cerr. Can I mix these outputs?

Seems no, I recommend to rewrite the code where you are using std::cerr << and replace it by "qDebug() <<", qWarning() <<, etc.

Another question about using QMutex. Basically, I am just reading the values set by the other thread, and starting/stopping the thread. Do I need to use QMutex in such simple case?

This question may be not so simple. For the simplest cases volatile may be enough. Read Synchronizing Threads.

(I mean I know why to use a mutex; my question is about that when using Qt, the internal mechanisms of GUI handling and thread handling may make it a need)

Qt doesn't affect to such programming basics. But please note that all GUI elements in Qt can be accessed only from the GUI thread. See Threading Basics in Qt:

As mentioned, each program has one thread when it is started. This thread is called the "main thread" (also known as the "GUI thread" in Qt applications). The Qt GUI must run in this thread. All widgets and several related classes, for example QPixmap, don't work in secondary threads. A secondary thread is commonly referred to as a "worker thread" because it is used to offload processing work from the main thread.

So you cannot access GUI elements directly from another thread.

textEdit->append("some text"); // may be directly called from a GUI thread only

Use Singal-Slot mechanism to access GUI elements from another threads. Read also Qt5 New Signal Slot Syntax.

You can also invoke methods using Qt::QueuedConnection without first connecting using QMetaObject::invokeMethod:

QMetaObject::invokeMethod(textEdit, "append", 
    Qt::QueuedConnection, 
    Q_ARG(QString, "some text"));

Read also: Multithreading Technologies in Qt:

Qt offers many classes and functions for working with threads. Below are four different approaches that Qt programmers can use to implement multithreaded applications...


Edit. Enhanced useful list of articles.

Signals and Slots
Singals and Slots in Qt
Qt5 New Signal Slot Syntax
QMetaObject::invokeMethod
How Qt Signals and Slots Work

Debugging
Qt Debuggin Techniques for c++

Threading
Threading Basics in Qt
Multithreading Technologies in Qt
Synchronizing Threads
Threads and Objects
The Missing Article About Qt Multithreading in C++
Threads Events QObjects


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...