1

I'm writing a Qt-based dll for plotting. The dll contains QApplication that runs in a separate std::thread. The main widget is class QPlotsControl : public QMainWindow.

void QtPlotter::ApplicationLoop(const WorkerThreadCallbackFn& cbFn)
{
    ...
    QApplication a(argc, &argv);
    ...
    m_qPlotsControl = new QPlotsControl(cbFn, [this](){ this->ReadyCallback(); });
    m_qPlotsControl->show();
    a.exec();
    delete m_qPlotsControl;
    ...
    return;
}

QtPlotter::QtPlotter(const WorkerThreadCallbackFn& cbFn):
    m_ready{false}, m_isFinished{false}
{
    std::thread guiThread([this,cbFn] { this->ApplicationLoop(cbFn); });
    guiThread.detach();
    ...
}

So QtPlotter is invoked from the outside, it creates a thread and QApplication runs in that thread. Everything worked fine until recently. Now, there is sometimes an error QObject::~QObject(): timers cannot be stopped from another thread and access violation at 0x000000C5 when I try to close the QPlotsControl and exit the ApplicationLoop. I understand that something is being deleted from a wrong thread, but can't locate what. After some time I found the source of troubles: crash occurs if I click recently added QPushButton. If I not, nothing happens and works/exits fine. Relevant code:

QPushButton *Ui_QPlotsControl::selectInstrumentsButton;
void Ui_QPlotsControl::setupUi(QMainWindow *QPlotsControl)
{
...
    selectInstrumentsButton = new QPushButton(centralWidget);
    selectInstrumentsButton->setObjectName(QString::fromUtf8("selectInstrumentsButton"));
    horizontalLayout->addWidget(selectInstrumentsButton);
...
}

void Ui_QPlotsControl::retranslateUi(QMainWindow *QPlotsControl)
{
    selectInstrumentsButton->setText(QCoreApplication::translate("QPlotsControl", "\320\222\321\213\320\261\320\276\321\200 \320\270\320\275\321\201\321\202\321\200\321\203\320\274\320\265\320\275\321\202\320\276\320\262", nullptr));
} // retranslateUi

QPlotsControl::QPlotsControl(const WorkerThreadCallbackFn& cbFunc, const readyCallbackFn& readyCb,
    QWidget *parent) : m_Callback{ cbFunc }, m_readyCallback { readyCb },
    m_needsClosing{false},
    QMainWindow(parent)
{
    ui.setupUi(this);
    ...
}

There is literally no more references to this button in the code now. Nothing connected. Nothing overloaded. If one clicks, error occurs on close. If one doesn't, everything is ok.

I'm sorry I can't provide a reproducible example, at least immediately. I use MSVC 2017 (v141) & qt5.14.1.
What I'm looking at?
Thank you very much.


EDIT#1
I found that the message about QObject::~QObject(): timers cannot be stopped from another thread is printed somewhere from ntdll thread. There are no Qt-based threads at this point. So it does mean, I suppose, that QApplication does not release all resources and is being destroyed not from "GUI"(or qt-main) thread. I wonder what is the difference between QPushButton and its click and QTabWidget (works fine).


EDIT#2
This is a threading problem. The minimal reproducible example is extremely simple:

#include <QApplication>
#include <QPushButton>
#include <thread>

void thrRoutine()
{
    int argc = 1;
    char *argv[] = { "gui\0" };
    QApplication a(argc, argv);
    QPushButton btn("Push");
    btn.show();
    a.exec();
}

int main(int argc, char *argv[])
{
    std::thread thr(thrRoutine);
    thr.join();
}

If one closes the window without interaction, everything is fine. If any interaction occured, including simple hovering the pointer over the button, QObject::~QObject: Timers cannot be stopped from another thread message will be printed and crash will occur in more sophisticated setups.
How to deal with it?

Suthiro
  • 1,210
  • 1
  • 7
  • 18
  • 2
    Re. `"The dll contains QApplication that runs in a separate std::thread"` -- as far as I'm aware having an instance of `QApplication` running on any thread other than that on which `main` is running is not supported. – G.M. Apr 25 '21 at 19:22
  • 3
    From the [documentation](https://doc.qt.io/qt-6/thread-basics.html) >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. Issue appears to be with the use of threading. – Aditya Singh Rathore Apr 25 '21 at 19:52
  • 1) It is a threading problem indeed. 2) It is totally possible to run `QApplication` in a separate thread. See [this answer on qt.io](https://forum.qt.io/topic/84485/qapplication-not-created-in-main-thread/7) and [this answer on SO](https://stackoverflow.com/questions/22289423/how-to-avoid-qt-app-exec-blocking-main-thread/22290909#22290909). Not to mention that everything worked fine for a year without that button. – Suthiro Apr 26 '21 at 07:14
  • Sorry but your last comment summarizes the problem perfectly. I've seen lots of people (and companies) get that thread model to 'work' and assume that everything will be just fine only for a simple change (the addition of a button in this case) to bring the whole thing crashing down. It's unsupported. You might get it working again but it will always be fragile. – G.M. Apr 26 '21 at 13:59
  • Maybe you're right, but I have no choice. I need dll with non-blocking main. – Suthiro Apr 26 '21 at 14:57
  • You should post the main problem you are trying to solve. Normally, you can create the application with the GUI on the main thread and then have implementation or time consuming processing on a separate thread. But it's unclear your purpose. For example, I have created a library that can take images to be processed in which it adds the job to a job queue to be done in a thread. Then the library isn't blocking at all. – slayer Apr 28 '21 at 01:45
  • @slayer Again, I need dll with non-blocking main. If Qt's message loop is created in the function that being invoked by an external application, the application will wait for return and hang. – Suthiro Apr 28 '21 at 15:26

0 Answers0