PDA

View Full Version : Message log console used in different threads



fcona
19th December 2017, 09:39
Hello,
Premise: about 6 months ago I changed my job, so I inherited some pretty big C++ project using qt libraries and I started learning qt that moment.

My issue: the project I'm working on consists of several sub-projects and most of them make use of multi-threading to handle asynchronous events from the GUI and from connected devices.
The guy who developed the project in the first place has implemented a message handler for qInfo, qDebug and qCritical in the following way:
main.cpp


Console * console;

void messageHandler(QtMsgType type, const QMessageLogContext &, const QString & msg) {
switch (type) {
case QtDebugMsg:
// formatting for debug messages
break;

case QtInfoMsg:
// formatting for info messages
break;

case QtCriticalMsg:
// formatting for critical messages
break;

default:
break;
}

if (console) {
console->addLine(msg);
}
}

int main(int argc, char * argv[]) {
QApplication a(argc, argv);
qInstallMessageHandler(QtMessageHandler(messageHan dler));

console = new Console();
MainWindow mainwindow;

mainwindow.showMaximized();
mainwindow.setConsole(console);
a.exec();
}

console.ui declares a QListWidget * consoleWidget for the class Console.
console.h:


namespace Ui {
class Console;
}

class Console : public QWidget {
Q_OBJECT

public:
explicit Console(QWidget * parent = 0);

void addLine(QString line);

private:
Ui::Console * ui;
};

console.cpp:


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

void Console::addLine(QString line) {
ui->consoleWidget->addItem(line);
ui->consoleWidget->scrollToBottom();
}


So the aim is to redirect all of the messages, properly formatted, to a console.
At some point I noticed that disabling the addLine on the console prevented a lot of extemporary crashes of the application, preceded by some warnings complaining about QTimers being started or stopped from different threads.
So I decided to disable the console with an ifdef, hoping to find a solution later on, and the warnings and crashes actually ceased.

Today I realized the problem may be due to the fact that qInfo, qDebug and qCritical are called all over the code, so they actually allow pieces of code running in different threads to directly call a method (addLine) on a GUI object (Console).
So I wanted to try and replace console->addLine(msg); within messageHandler with a signal emit to keep safe the GUI thread, but I don't know how to do it, since messageHandler is a non-member function so, afaik, it cannot emit signals. Moreover, I think that if I created a class and made messageHandler one of its members I could experience problems due to having the method of a single object called in different threads.

So lastly, my questions:
1) Is my analysis of the problem correct or the crashes may be due to something else?
2) Is the requirement of redirecting all messages to a console something intrinsically wrong and should I give up to a different solution for my logs?
3) If no, what would be the proper implementation to obtain such behaviour?

Thanks for your help and best regards!

fcona
19th December 2017, 11:52
Maybe I solved the problem on my own.
I found that QMetaObject::invokeMethod(QObject *object, const char *member, Qt::ConnectionType type), called with type = Qt::QueuedConnection, posts an event to object, that tells object to execute member as soon as it enters its main event loop.
So I tried to substitute


console->addLine(msg);

with


QMetaObject::invokeMethod(console, "addLine", Qt::QueuedConnection, Q_ARG(QString, msg));

and declaring addLine as Q_INVOKABLE, and now my application seems to work fine even if I spam the message logger from a while(true) loop living in its own thread.
Of course, with the spam active, the application slows down, but it doesn't crash and doesn't write the warnings I talked about in the previous post: for the sake of completeness they are
"QBasicTimer::stop: Failed. Possibly trying to stop from a different thread" and
"QBasicTimer::start: Timers cannot be started from another thread" I've seen them now for the first time after a long while...

So now my question becomes: am I doing something horribly wrong which I will pay for in the long run or am I just doing things right?

Bests