3

In my App I have some problems with threads and GUI messages like QMessageBox or a new dialog. To reproduce I made a small app to show the problem:

mainwindow.cpp

#include <QDebug>
#include "mainwindow.h"
#include "ui_mainwindow.h"

void ThreadAddTree::run() {

    //mClass->addTreeEx();
    bool b = false;
    emit addTree(&b);
}


MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_pushButton_clicked()
{
    QString path = "";
    mThreadAddTree = new ThreadAddTree(this, path);
    connect(mThreadAddTree, SIGNAL(addTree(bool*)), this, SLOT(on_add_tree(bool*)), Qt::BlockingQueuedConnection);
    //,Qt::DirectConnection
    mThreadAddTree->start();
}

void MainWindow::on_add_tree(bool* newData) {

    QMessageBox::information(this, tr("Information"),
                             tr("Button click!"));

    *newData = true;


}

void MainWindow::addTreeEx()
{
    QMessageBox::information(this, tr("Information"),
                             tr("Button click!"));
}

Mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtWidgets>
#include <QThread>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class ThreadAddTree : public QThread
{
  Q_OBJECT
public:
  ThreadAddTree(class MainWindow *nClass, const QString &path) {
    mPath = path;
    mClass = nClass;
  }
signals:
  void addTree(bool*);
protected:
  void run();
  QString mPath;
  class MainWindow *mClass;
};

class MainWindow : public QMainWindow
{
    Q_OBJECT
    friend class ThreadAddTree;

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

    void addTreeEx();

private slots:
    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;

protected:
     ThreadAddTree *mThreadAddTree;


protected Q_SLOTS:
     void on_add_tree(bool* newData);
};
#endif // MAINWINDOW_H

main.cpp

#include "mainwindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

If I use in Thread the call: mClass->addTreeEx(); the app will crash, in case of non main GUI thread. Understood.

So I uncouple the call with emit a message emit addTree(&b); works well. Messagebox is shown and no crash.

But now it becomes complicated for me. I need to call mClass->addTreeEx(); in my app because it will do a couple of operations. The same function is also used outside an additional thread. But in one case, the mClass->addTreeEx(); that is running inside the thread need to call the Messagebox.

So my question is here, how to manage, that I can emit the emit addTree(&b); from the function mClass->addTreeEx(); if it was called from the thread and the app will not crash in case of no GUI thread?

ingo
  • 776
  • 1
  • 10
  • 25

2 Answers2

0

Interpreting the code author's intent (to degree) and open to be corrected.

  • The signal parameter of type bool* in void addTree(bool*); makes not much sense especially for the case with signal sender having the boolean variable on the stack. Either make it void addTree(bool) and send immediate value or just void addTree() and handle the boolean atomic flag on the slot side as std::atomic_bool thread_safe_flag; so checking on that flag will be as actual as possible if (thread_safe_flag). But such handling requires even more to ensure the signal delivered at the right time in sync with the value. This is irrelevant to Qt, though: Why do I need to acquire a lock to modify a shared "atomic" variable before notifying condition_variable and can be done either with or without Qt.

  • The problem of message box on UI thread preventing the other message box from appearing (this is again an interpretation of the author's problem with the code). We obviously need to have a handle operate the message box, say, dismiss it, in case if it is open already:

    QMessageBox* m_msgBoxPtr{nullptr};
    std::atomic_bool m_threadSafeFlag;
    
    void UI_Class::mySlotToHandleMsgBox()
    {
       if (m_msgBoxPtr != nullptr) {
           m_msgBoxPtr->close();
           m_msgBoxPtr->deleteLater();
           m_msgBoxPtr = nullptr;
           mySlotToHandleMsgBox();
       }
       else {
           m_msgBoxPtr = new QMessageBox(QMessageBox::Information, title, message);
           m_msgBoxPtr->exec(); // assuming we want modal dialog as QMessageBox::information()
           // otherwise do m_msgBoxPtr->show()
    
           // if this is set on UI thread only then and no
           // "waits" for it on other threads then it being atomic is enough;
           // then don't bother with any sync "complications"
           m_threadSafeFlag = true;
       }
    }
    
Alexander V
  • 8,351
  • 4
  • 38
  • 47
-2

mainworker.h

#ifndef MAINWORKER_H
#define MAINWORKER_H

#include <QObject>

class MainWorker : public QObject
{
    Q_OBJECT

signals:
    void completed(void);
public slots:
    void run(void);
};

#endif // MAINWORKER_H

mainworker.cpp

#include "mainworker.h"
#include <QThread>

void MainWorker::run(void)
{
    QThread::sleep(1);
    emit completed();
}

mainthread.h

#ifndef MAINTHREAD_H
#define MAINTHREAD_H

#include <QThread>

class MainThread : public QThread
{
    Q_OBJECT

public:
    MainThread(void);
};

#endif // MAINTHREAD_H

mainthread.cpp

#include "mainthread.h"

MainThread::MainThread(void)
    : QThread(nullptr)
{
}

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QPushButton>

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(void);
signals:
    void runJob(void);
public slots:
    void jobCompleted(void);
private:
    QPushButton m_runButton;
};

#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include <QMessageBox>

MainWindow::MainWindow(void)
    : QMainWindow(nullptr),
      m_runButton(this)
{
    connect(&m_runButton, SIGNAL(released()), this, SIGNAL(runJob()));
    m_runButton.setText("RUN!");
    setCentralWidget(&m_runButton);
}

void MainWindow::jobCompleted(void)
{
    QMessageBox::information(this, tr("Info"), tr("Job completed!"));
}

main.cpp

#include <QApplication>
#include "mainwindow.h"
#include "mainthread.h"
#include "mainworker.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow window;
    MainThread thread;
    MainWorker worker;

    worker.connect(&window, SIGNAL(runJob()), &worker, SLOT(run()));
    window.connect(&worker, SIGNAL(completed()), &window, SLOT(jobCompleted()));

    worker.moveToThread(&thread);
    thread.start();
    window.show();

    int exitCode = a.exec();

    thread.quit();
    thread.wait();

    return exitCode;
}

Of course you can add args to signals and slots and call jobCompleted() slot any time from GUI thread.

Deep
  • 2,472
  • 2
  • 15
  • 25
  • Iam sorry to say, but I do not understand how to apply your code to my sample or finally to my app. – ingo Dec 28 '19 at 17:08
  • 1
    Code-only answers tend to explain nothing, making them not useful in the long run. – JaMiT Jan 01 '20 at 22:41
  • But an answer need to be done in the scope of the question. And in my eyes, this code makes no sense. Sorry. – ingo Jan 04 '20 at 18:13
  • Sorry.BTW: No sense in the scope of my problem. – ingo Jan 04 '20 at 18:19
  • @ingo all your code BIG PROBLEM, I show you how correct use threads, workers and signals between that objects, and for additional, if you want multiple worked threads use https://doc.qt.io/qt-5/qthreadpool.html – Deep Jan 10 '20 at 11:29