QtConcurrent и исключения

Опубликовано SergeyOs - вт, 03/31/2020 - 22:11

При использовании QtConcurrent и исключений из стандартной библиотеки, текст исключения теряется, остаётся только информация о типе исключений. Например результатом такого кода:

#include <QtConcurrent/QtConcurrent>
#include <QFuture>
#include <QDebug>
 
int main(){
    auto f = QtConcurrent::run([]()->bool{
            qDebug() << "concurrent start";
            throw std::runtime_error("error");
            return true;
          });
    try{
        bool r = f.result();
    }
    catch(std::exception& e){
        qDebug() << e.wath();
    }
}

будет такое:

concurrent start
std::exception

текст исключения исчез. Такого не произойдёт если мы использовать вместо Qt функционала, функционал из std:

#include <future>
#include <QDebug>
 
int main(){
    auto f = std::async([]()->bool{
        qDebug() << "async start";
        throw  std::runtime_error("error");
        return true;
    });
 
    try {
        bool r = f.get();
    } catch (std::exception& e) {
        qDebug() << e.what();
    }
 
    return 0;
}

результат будет:

async start
error

Но в этом случае теряется вся прелесть программирования на Qt, тем более если весь проект написан с использованием этого фреймворка. Например, можно QFuture обернуть в QFutureWatcher, и использовать мощь сигналов/слотов:

    QEventLoop loop;
    QFutureWatcher<bool> fw;
    connect(&fw, &QFutureWatcher<bool>::finished, &loop, &QEventLoop::quit); 
    fw.setFuture(QtConcurrent::run([]()->bool{ return true; }));

Поэтому, чтобы избежать потери текста исключения его надо сохранить в свой тип исключения, наследованный от QException. QException спокойно передаётся между тредами в Qt. Как-то так:

class MyException : public QException
{
public:
    explicit MyException(const char* msg) : msg_(msg) {}
    void raise() const override { throw *this; }
    MyException* clone() const override { return new MyException(*this); }
    const char* message() const { return msg_.data(); }
private:
    QByteArray msg_;
};

Теперь можно использовать его так: в асинхронном вызове делается перехват стандартного исключения, и бросается кастомное, на основе QException. Немного криповато выглядит, но главное - работает.

int main(){
    auto f = QtConcurrent::run([]()->bool{
         try {
             qDebug() << "concurrent start";
             throw std::runtime_error("error");
         } catch (std::exception& e) {
             throw MyException(e.what());
         }
    });
 
    try {
        bool r = f.result();
    } catch (MyException& e) {
        qDebug() << e.message();
    }
    catch(QUnhandledException& e){
        qDebug() << "unknown error";
    }
}

Если верить документации, так можно передать исключение для следующих функций:

QFuture::waitForFinished()
QFuture::result()
QFuture::resultAt()
QFuture::results()