3

Background

I have a dialog which runs a time-consuming operation on its initialization. I wrapped this operation into an asynchronous function in order not to freeze the GUI.

Example

Imagine a dialog/widget which shows the current weather fetched asynchronously from a remote server:

Dialog::Dialog()
{
    auto label = new QLabel(this);
    QtConcurrent::run([=]() {
        const int temperature = getWeather(); // Time-consuming function
        label->setText(temperature);
    });
    // The rest code, layouts initialization, etc.
}

Problem

If this dialog/widget is closed before the asynchronous operation is finished, the label->setText() part will obviously lead to a crash because the widget object won't exist by that moment.

Question

What is the proper way to deal such situations? Probably, I should use something else instead of QtConcurrent (QThread, for example) in order to properly cancel the asynchronous function when the dialog is closed.

Note

Note that the actual code is about reading a bunch of files, not about networking, that's why using the async QNetworkRequest interface is not a case.

John Doe
  • 555
  • 6
  • 17
  • 2
    I think `QtConcurent::run()` doesn't fit to your approach, because, as Qt docs say: "QtConcurrent::run() does not support canceling, pausing, or progress reporting...". You might use `QThread` or just prevent your dialog from closing. – vahancho Dec 02 '19 at 13:44

2 Answers2

4
// global or class member
QFutureWatcher<int> g_watcher;

Dialog::Dialog()
{
    connect(&g_watcher, &QFutureWatcher<int>::finished, this, &Dialog::handleFinished);

    QFuture<int> future = QtConcurrent::run([]() -> int
    {
        int temperature = getWeather(); // Time-consuming function

        return temperature;
    });

    g_watcher.setFuture(future);
}

void Dialog::handleFinished()
{
    // will never crash because will not be called when Dialog destroyed
    ui->label->setText(QString::number(g_watcher.result()));
}

There is also possible to disconnect everything connected to the finished signal:

disconnect(&g_watcher, &QFutureWatcher<int>::finished, 0, 0);

p.s. As for cancel the async operation, it cannot be cancelled correctly by QtConcurrent or QThread methods.

There is QThread::terminate() method, but from the doc:

...Warning: This function is dangerous and its use is discouraged. The thread can be terminated at any point in its code path...

Therefore you have to implement some "cancel" flag inside your getWeather() function, or do as written above.

Vladimir Bershov
  • 2,701
  • 2
  • 21
  • 51
0

QtConcurrent::run() will return you a QFuture<T>. You should be able to call QFuture::waitForFinished on it.

The other thing is that you should not be allowed to call QLabel::setText from another thread, use QMetaObject::invokeMethod instead or emit a signal.

Zaiborg
  • 2,492
  • 19
  • 28
  • Doesn't the `QFuture::waitForFinished` go against the the initial idea of preventing the GUI freeze described in the **Background** section? Regarding your second remark, I'll check it, thanks. – John Doe Dec 02 '19 at 13:54
  • Not 100% sure but when the application is quitting, the user will not notice the freeze in a hidden window. I would wait for the future to be finished when the application is quit or when your widgets dtor is called. – Zaiborg Dec 02 '19 at 14:01
  • Unfortunately, this is not a main window, just a dialog. Closing it does not quit the application, so the `QFuture::waitForFinished` will block the GUI. – John Doe Dec 02 '19 at 14:29