Search code examples
c++multithreadingqtexceptionqthread

What's the proper way of catching an exception of method running in a different thread?


I am running a qml application with C++ plugin. Application is pretty simple:

QApplication app(argc, argv);
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:///ui/views/mainwindow.qml")));

return app.exec();

But qml plugin has a lot of code. To avoid freezes in qml I place object into a thread by moveToThread() and call methods by QMetaObject::invokeMethod() asynchronously by using Qt::QueuedConnection parameter. The problem is the methods I call by invokeMethod can throw an exception(s) and then the program will crash because I can't catch them:

try {
    QMetaObject::invokeMethod(&qlNetwork, "disconnect", Qt::QueuedConnection);
} catch (const std::runtime_error& e) {
    emit error(e.what());
}

Of course this code will not work because the call is non-blocking. The question is: how then can I catch exceptions from an object in different thread (QThread) ?


Solution

  • You'd create a wrapper slot that calls disconnect from another thread and handles the exception.

    void ThisClass::wrapperMethod() {
        try {
            qlNetwork->disconnect();
        } catch (const std::runtime_error& e) {
            emit error(e.what());
        }
    }
    

    And then you invoke the wrapper method asynchronously:

        QMetaObject::invokeMethod(this, "wrapperMethod", Qt::QueuedConnection);
    

    Make sure wrapperMethod is a SLOT or it is defined as Q_INVOKABLE and ThisClass instance is moved to a different thread.


    Possible solution using lambdas

    QTimer *t = new QTimer();
    connect(t, &QTimer::timeout, this, [=]() {
        t->deleteLater();
        try {
            qlNetwork->disconnect();
        } catch (const std::runtime_error& e) {
            emit this->error(e.what());
        }
    }, Qt::QueuedConnection);
    /* don't forget to move the timer to the thread where
       you want the lambda to be executed*/
    t->moveToThread(targetThread);
    t->setSingleShot(true);
    t->start(0);
    

    Solution using lambdas with QtConcurrent (Victor Polevoy)

    void ThisClass::performDisconnect() {
        QtConcurrent::run([this]() {
            try {
                this->qlNetwork.disconnect();
            } catch (const std::runtime_error& e) {
                emit error(e.what());
            }
        });
    }