Как создать сигнал qDebug из другого потока в поток Qt5 GUI - PullRequest
0 голосов
/ 19 марта 2020

Я пытаюсь отобразить сообщения журнала из рабочего потока в GUI. Я пытаюсь следовать

перенаправить qDebug в QTextEdit

Он начал работать нормально, но я застрял, как программировать

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

Принцип, который я вижу, заключается в том, что один сигнал в потоке должен быть подключен к слоту в потоке GUI; но как вызвать этот сигнал? Кроме того, я делаю некоторые записи с QDebug, а также вывод на std::cerr. Могу ли я смешать эти выводы? (Я имею в виду, вероятно, я сделаю другой сигнал, но я должен передать sh сообщения или я могу использовать один экземпляр Qt::QueuedConnection)

Другой вопрос об использовании QMutex. По сути, я просто читаю значения, установленные другим потоком, и запускаю / останавливаю протектор. Нужно ли использовать QMutex в таком простом случае? (Я имею в виду, я знаю, зачем использовать мьютекс; мой вопрос о том, что при использовании Qt внутренние механизмы обработки GUI и обработки потоков могут сделать это необходимым)

Моя пробная версия на самом деле является демонстрационной 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);
    }
}

Соединение, которое я выполняю в конструкторе QMainWindow, перед resize ().

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

Я использую

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

На самом деле, я было лениво, и я вставил общий запуск в поле «About».

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

Поток возвращается, когда l oop в потоке закончился. Я не получаю сообщение qInfo (), и если я ставлю точку останова после строки qInfo (), я получаю сообщение в сообщении приложения Qt Creator.

RTTI symbol not found for class 'QObject'

Если у меня есть точка останова, Я не получаю свои сообщения в окне GUI. Если я запускаю его без точки останова, я делаю это, но только когда l oop закончен, и в этот раз «RTTI символ не найден».

Что-то должно быть не так с синхронизацией. Даже с точкой останова она также замораживает мою систему.

1 Ответ

1 голос
/ 19 марта 2020

но как вызвать этот сигнал?

Объявите сигнал в интерфейсе класса QObject. Вставьте макрос Q_OBJECT в объявление класса. Прочитайте Singals и Slots в Qt .

Пример. Подкласс QThread в вашем случае (QThread наследует QObject). Прочитайте Основы работы с потоками в Qt и Документы QThread .

Заголовок

#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;
};

испускают сигнал из места, где вы нужно:
Исходный файл

#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 заголовочный файл класса

#include <QtWidgets/QMainWindow>

class MainWin : public QMainWindow
{
    Q_OBJECT

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

GUI класс исходный файл

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

Сделайте то же самое, используя высокоуровневый 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;
}

Пожалуйста, также обратите внимание, что Qt предоставляет единое место для управления всеми сообщениями отладки, предупреждений, ошибок и других типов:

Методы отладки Qt для c ++
qInstallMessageHandler ()

Теперь вы можете установить обработчик событий один раз, и тогда все сообщения будут go в одном месте, где вы сможете выводить их там, где это необходимо, без использования пользовательских подключений.

Обратите внимание, что Qt предоставляет несколько глобальных макросов для записи предупреждения и отладочного текста:

qDebug() используется для записи пользовательских отладочных выходных данных.
qInfo() is u sed для информационных сообщений.
qWarning() используется для сообщения о предупреждениях и исправляемых ошибках в вашем приложении.
qCritical() используется для записи критических сообщений об ошибках и сообщения о системных ошибках.
qFatal() используется для написание фатальных сообщений об ошибках незадолго до выхода.

.

Кроме того, я веду некоторые записи с QDebug, а также вывод на std::cerr. Могу ли я смешать эти выходы?

Кажется нет, я рекомендую переписать код, в котором вы используете std::cerr <<, и заменить его на "qDebug() <<", qWarning() <<, et c.

Еще один вопрос по использованию QMutex. По сути, я просто читаю значения, установленные другим потоком, и запускаю / останавливаю поток. Нужно ли использовать QMutex в таком простом случае?

Этот вопрос может быть не таким простым. Для простейших случаев volatile может быть достаточно. Читать Синхронизация потоков .

(я имею в виду, я знаю, зачем использовать мьютекс; мой вопрос о том, что при использовании Qt, внутренних механизмов обработки GUI и обработки потоков может потребоваться)

Qt не влияет на такие основы программирования. Но обратите внимание, что все элементы GUI в Qt доступны только из потока GUI. Смотрите Основы потоков в Qt :

Как уже упоминалось, каждая программа имеет один поток при запуске. Этот поток называется "основным потоком" (также известным как "GUI поток" в приложениях Qt). Qt GUI должен работать в этом потоке. Все виджеты и несколько связанных классов, например QPixmap, не работают во вторичных потоках. Вторичный поток обычно называют «рабочим потоком», потому что он используется для разгрузки обработки обработки из основной поток.

Таким образом, вы не можете получить доступ к элементам GUI напрямую из другого потока.

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

Использовать Singal-Slot механизм для доступа к GUI элементы из других потоков. Читайте также Синтаксис нового слота сигналов Qt5 .

Вы также можете вызывать методы, используя Qt::QueuedConnection без первого подключения, используя QMetaObject :: invokeMethod :

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

Читайте также: Технологии многопоточности в Qt :

Qt предлагает множество классов и функций для работы с потоками. Ниже приведены четыре различных подхода, которые программисты Qt могут использовать для реализации многопоточных приложений ...


Edit. Расширенный полезный список статей.

Сигналы и слоты
Singals и слоты в Qt
Qt5 Синтаксис нового слота сигналов
QMetaObject :: invokeMethod
Как работают сигналы и слоты Qt

Отладка
Методы отладки Qt для c ++

Threading
Основы работы с потоками в Qt
Технологии многопоточности в Qt
Синхронизация потоков
Потоки и объекты
Отсутствующая статья о многопоточности Qt в C ++
Потоки События QObjects

...