How to propagate exceptions to the main thread from a QtConcurrent::run with promise

201 Views Asked by At

I'm experimenting with the concurrent run with promise functionality from Qt6. How can I catch, from the GUI thread, any exception thrown in the worker thread?

void MainWindow::on_pushButton_startTask_clicked()
{
    auto lambda = [](QPromise<void>& promise)
    {
        // Cancel the task if requested
        while (!promise.isCanceled())
        {
            // Do some long computation...

            if (<something-has-failed>)
                throw std::runtime_error("The task has failed");
        }
    };

    this->future = QtConcurrent::run(lambda);
    // Connect the watcher to the finished signal
    connect(&this->watcher, &QFutureWatcher<void>::finished, this, &MainWindow::on_taskFinished);
    this->watcher.setFuture(this->future);
}

void MainWindow::on_taskFinished()
{
    // How to catch the exception here?
}

I've tried by doing the following two things:

  1. Adding onFailed in the on_taskFinished:
void MainWindow::on_taskFinished()
{
    this->future.onFailed([](const std::exception& ex)
    {
        std::cout << ex.what();
    });
}
  1. Adding onFailed after calling QtConcurrent::run:
void MainWindow::on_pushButton_startTask_clicked()
{
    ...

    this->future = QtConcurrent::run(lambda)
        .onFailed([](const std::exception& ex)
        {
            std::cout << ex.what();
        });
    ...
}

In both cases the code passes from the onFailed block, but the exception is not the one thrown from the lambda, it looks like an empty default excetion (maybe something from Qt?), whose what content is Unknown exception.

1

There are 1 best solutions below

0
Gelldur On

You may use onFailed with app context e.g.

QtConcurrent::run(lambda).onFailed(qApp, [](const std::exception& ex) { 
    // executed on main thread
});

qApp is just macro for: QCoreApplication::instance()


It is little bit tricky to use QtConcurrent::run prefer to use explicit context. Otherwise you may end up with issues like me when lambda executes so quick that you will "inherit wrong context". E.g.

QtConcurrent::run(...).then( this may be executed on main thread "it depends" )

so prefer using: (or with QObject* context)

QtConcurrent::run(...).then(QtFuture::Launch::Async, always "in background")

Watchout for: QUnhandledException Example how to handle it (to unwrap your exception):

        .onFailed(qApp,
                  [](const QUnhandledException& ex) {
                      llog::error() << "QUnhandledException: " << (ex.exception() != nullptr)
                                    << " thread:" << std::this_thread::get_id();
                      try
                      {
                          std::rethrow_exception(ex.exception());
                      }
                      catch(const std::exception& rethrowEx)
                      {
                          llog::error() << "Rethrow exception: " << rethrowEx.what()
                                        << " thread:" << std::this_thread::get_id();

                          QMessageBox{QMessageBox::Icon::Critical,
                                      "QUnhandledException - std::exception",
                                      rethrowEx.what(),
                                      QMessageBox::StandardButton::Ok}
                              .exec();
                      }
                      catch(...)
                      {
                          llog::error() << "unknown handle";
                          QMessageBox{QMessageBox::Icon::Critical,
                                      "UnhandledException",
                                      "Error in unhandled",
                                      QMessageBox::StandardButton::Ok}
                              .exec();
                      }
                  })