Improve message handling from threads.

Only GUI thread can show message boxes.

--HG--
branch : release
This commit is contained in:
Roman Telezhynskyi 2018-09-28 21:05:42 +03:00
parent 10a20b08b8
commit 47e3f9f7c7
6 changed files with 238 additions and 14 deletions

View file

@ -37,6 +37,7 @@
#include "../vmisc/logging.h"
#include "../vmisc/vsysexits.h"
#include "../vmisc/diagnostic.h"
#include "../vmisc/qt_dispatch/qt_dispatch.h"
#include "../qmuparser/qmuparsererror.h"
#include <QDir>
@ -65,7 +66,22 @@ QT_WARNING_POP
//---------------------------------------------------------------------------------------------------------------------
inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
Q_UNUSED(context)
// only the GUI thread should display message boxes. If you are
// writing a multithreaded application and the error happens on
// a non-GUI thread, you'll have to queue the message to the GUI
QCoreApplication *instance = QCoreApplication::instance();
const bool isGuiThread = instance && (QThread::currentThread() == instance->thread());
if (not isGuiThread)
{
auto Handler = [](QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
noisyFailureMsgHandler(type, context, msg);
};
q_dispatch_async_main(Handler, type, context, msg);
return;
}
// Why on earth didn't Qt want to make failed signal/slot connections qWarning?
if ((type == QtDebugMsg) && msg.contains(QStringLiteral("::connect")))
@ -130,12 +146,6 @@ inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &con
type = QtDebugMsg;
}
// only the GUI thread should display message boxes. If you are
// writing a multithreaded application and the error happens on
// a non-GUI thread, you'll have to queue the message to the GUI
QCoreApplication *instance = QCoreApplication::instance();
const bool isGuiThread = instance && (QThread::currentThread() == instance->thread());
switch (type)
{
case QtDebugMsg:

View file

@ -39,6 +39,7 @@
#include "../vmisc/vmath.h"
#include "../qmuparser/qmuparsererror.h"
#include "../mainwindow.h"
#include "../vmisc/qt_dispatch/qt_dispatch.h"
#include <QtDebug>
#include <QDir>
@ -65,6 +66,23 @@ Q_DECL_CONSTEXPR auto DAYS_TO_KEEP_LOGS = 3;
//---------------------------------------------------------------------------------------------------------------------
inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
// only the GUI thread should display message boxes. If you are
// writing a multithreaded application and the error happens on
// a non-GUI thread, you'll have to queue the message to the GUI
QCoreApplication *instance = QCoreApplication::instance();
const bool isGuiThread = instance && (QThread::currentThread() == instance->thread());
if (not isGuiThread)
{
auto Handler = [](QtMsgType type, const QMessageLogContext &context, const QString &msg)
{
noisyFailureMsgHandler(type, context, msg);
};
q_dispatch_async_main(Handler, type, context, msg);
return;
}
// Why on earth didn't Qt want to make failed signal/slot connections qWarning?
if ((type == QtDebugMsg) && msg.contains(QStringLiteral("::connect")))
{
@ -128,12 +146,6 @@ inline void noisyFailureMsgHandler(QtMsgType type, const QMessageLogContext &con
type = QtDebugMsg;
}
// only the GUI thread should display message boxes. If you are
// writing a multithreaded application and the error happens on
// a non-GUI thread, you'll have to queue the message to the GUI
QCoreApplication *instance = QCoreApplication::instance();
const bool isGuiThread = instance && (QThread::currentThread() == instance->thread());
{
QString debugdate = "[" + QDateTime::currentDateTime().toString(QStringLiteral("yyyy.MM.dd hh:mm:ss"));

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Recep ASLANTAS
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,78 @@
Qt-Dispatch
===========
Execute block on the main thread or specified thread with Qt framework.
No required SIGNAL/SLOTs, no required QObject::connect...
Use with functions or C++11 Lamdas
Functions
------------
```cpp
static void q_dispatch_async_main(fnBlock block); // Execute on Main / UI thread.
static void q_dispatch_async(QThread* thread, fnBlock block); // Execute on some thread.
```
SAMPLE USAGE
------------
```cpp
#include "qt_dispatch.h"
// Usage 1: Using with lambdas
// This function called from non-ui/background thread.
void MainWindow::handleSockData(void *obj) {
// This usage requires C++11
q_dispatch_async_main([&]() {
// Add widgets to window using ui object.
SomeWidget *widget = new SomeWidget();
ui->someVLayout->addWidget(widget);
// check the thread id
const bool isGuiThread = QThread::currentThread() ==
QCoreApplication::instance()->thread();
qWarning() << "isGuiThread: " << isGuiThread; // true
});
}
// Usage 2: Using with functions
void MainWindow::handleSockData(void *obj{
void somefunc();
q_dispatch_async_main(somefunc);
}
void somefunc() {
const bool isGuiThread = QThread::currentThread() ==
QCoreApplication::instance()->thread();
qWarning() << "isGuiThread: " << isGuiThread; // true
}
// Usage 3: Using with other threads
void MainWindow::on_action1_triggered() {
QThread *newThread = new QThread();
newThread->start();
q_dispatch_async(newThread, []() {
const bool isGuiThread = QThread::currentThread() ==
QCoreApplication::instance()->thread();
qWarning() << "isGuiThread: " << isGuiThread; // false
});
}
```
Enabling C++11 Compiler Support: Add these lines to your .pro file
```
CONFIG += c++11
QMAKE_CXXFLAGS += -std=c++11 -stdlib=libc++ -mmacosx-version-min=10.7
LIBS += -stdlib=libc++ -mmacosx-version-min=10.7
```
###Enjoy!

View file

@ -0,0 +1,103 @@
//
// Created by Recep ASLANTAS
// Copyright (c) 2013 Recep ASLANTAS. All rights reserved.
//
#ifndef THREAD_DISPATCHER_H
#define THREAD_DISPATCHER_H
#include <QThread>
#include <QMetaObject>
#include <QThread>
#include <QCoreApplication>
#include <QObject>
#include <functional>
typedef std::function<void()> voidBlock;
class WorkerClass : public QObject
{
Q_OBJECT
public:
WorkerClass(QThread *thread)
{
moveToThread(thread);
connect(QThread::currentThread(), &QThread::finished, this, &WorkerClass::deleteLater);
}
public slots:
void DoWork(voidBlock block)
{
block();
deleteLater();
}
};
static void q_dispatch_async(QThread* thread, voidBlock block)
{
qRegisterMetaType<voidBlock>("voidBlock");
WorkerClass *worker = new WorkerClass(thread);
QMetaObject::invokeMethod(worker, "DoWork", Qt::QueuedConnection, Q_ARG(voidBlock, block));
}
static void q_dispatch_async_main(voidBlock block)
{
QThread *mainThread = QCoreApplication::instance()->thread();
q_dispatch_async(mainThread, block);
}
typedef std::function<void(QtMsgType, const QMessageLogContext &, const QString &)> msgHandlerBlock;
class MsgHandlerWorkerClass : public QObject
{
Q_OBJECT
public:
MsgHandlerWorkerClass(QThread *thread, QtMsgType type, const QMessageLogContext &context, const QString &msg)
: m_type(type),
m_msg(msg),
m_line(context.line),
m_file(context.file),
m_function(context.function),
m_category(context.category)
{
moveToThread(thread);
connect(QThread::currentThread(), &QThread::finished, this, &WorkerClass::deleteLater);
}
public slots:
void DoWork(msgHandlerBlock block)
{
block(m_type, QMessageLogContext(qUtf8Printable(m_file), m_line, qUtf8Printable(m_function),
qUtf8Printable(m_category)), m_msg);
deleteLater();
}
private:
QtMsgType m_type;
QString m_msg;
// We cannot make copy of QMessageLogContext. So, we must save its data instead and recreate it later.
int m_line;
QString m_file;
QString m_function;
QString m_category;
};
static void q_dispatch_async(QThread* thread, msgHandlerBlock block, QtMsgType type, const QMessageLogContext &context,
const QString &msg)
{
qRegisterMetaType<msgHandlerBlock>("msgHandlerBlock");
MsgHandlerWorkerClass *worker = new MsgHandlerWorkerClass(thread, type, context, msg);
QMetaObject::invokeMethod(worker, "DoWork", Qt::QueuedConnection, Q_ARG(msgHandlerBlock, block));
}
static void q_dispatch_async_main(msgHandlerBlock block, QtMsgType type, const QMessageLogContext &context,
const QString &msg)
{
QThread *mainThread = QCoreApplication::instance()->thread();
q_dispatch_async(mainThread, block, type, context, msg);
}
#endif // THREAD_DISPATCHER_H

View file

@ -38,7 +38,8 @@ HEADERS += \
$$PWD/defglobal.h \
$$PWD/backport/qoverload.h \
$$PWD/testvapplication.h \
$$PWD/literals.h
$$PWD/literals.h \
$$PWD/qt_dispatch/qt_dispatch.h
# Qt's versions
# 5.2.0, 5.2.1