From 47e3f9f7c7d51c54d453ac49e185318ddd547365 Mon Sep 17 00:00:00 2001 From: Roman Telezhynskyi Date: Fri, 28 Sep 2018 21:05:42 +0300 Subject: [PATCH] Improve message handling from threads. Only GUI thread can show message boxes. --HG-- branch : release --- src/app/tape/mapplication.cpp | 24 ++++-- src/app/valentina/core/vapplication.cpp | 24 ++++-- src/libs/vmisc/qt_dispatch/LICENSE.txt | 20 +++++ src/libs/vmisc/qt_dispatch/README.md | 78 +++++++++++++++++ src/libs/vmisc/qt_dispatch/qt_dispatch.h | 103 +++++++++++++++++++++++ src/libs/vmisc/vmisc.pri | 3 +- 6 files changed, 238 insertions(+), 14 deletions(-) create mode 100644 src/libs/vmisc/qt_dispatch/LICENSE.txt create mode 100644 src/libs/vmisc/qt_dispatch/README.md create mode 100644 src/libs/vmisc/qt_dispatch/qt_dispatch.h diff --git a/src/app/tape/mapplication.cpp b/src/app/tape/mapplication.cpp index b3332f578..0eff774b3 100644 --- a/src/app/tape/mapplication.cpp +++ b/src/app/tape/mapplication.cpp @@ -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 @@ -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: diff --git a/src/app/valentina/core/vapplication.cpp b/src/app/valentina/core/vapplication.cpp index 6751f6577..d534bbd34 100644 --- a/src/app/valentina/core/vapplication.cpp +++ b/src/app/valentina/core/vapplication.cpp @@ -39,6 +39,7 @@ #include "../vmisc/vmath.h" #include "../qmuparser/qmuparsererror.h" #include "../mainwindow.h" +#include "../vmisc/qt_dispatch/qt_dispatch.h" #include #include @@ -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")); diff --git a/src/libs/vmisc/qt_dispatch/LICENSE.txt b/src/libs/vmisc/qt_dispatch/LICENSE.txt new file mode 100644 index 000000000..56fbeb158 --- /dev/null +++ b/src/libs/vmisc/qt_dispatch/LICENSE.txt @@ -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. diff --git a/src/libs/vmisc/qt_dispatch/README.md b/src/libs/vmisc/qt_dispatch/README.md new file mode 100644 index 000000000..a55fb9eb8 --- /dev/null +++ b/src/libs/vmisc/qt_dispatch/README.md @@ -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! diff --git a/src/libs/vmisc/qt_dispatch/qt_dispatch.h b/src/libs/vmisc/qt_dispatch/qt_dispatch.h new file mode 100644 index 000000000..744ea35d8 --- /dev/null +++ b/src/libs/vmisc/qt_dispatch/qt_dispatch.h @@ -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 +#include +#include +#include +#include + +#include + +typedef std::function 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"); + + 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 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"); + + 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 diff --git a/src/libs/vmisc/vmisc.pri b/src/libs/vmisc/vmisc.pri index e6a02431c..338fb0990 100644 --- a/src/libs/vmisc/vmisc.pri +++ b/src/libs/vmisc/vmisc.pri @@ -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