John Doe
John Doe

Reputation: 635

How to terminate async function when the Qt dialog is closed

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.

Upvotes: 3

Views: 510

Answers (2)

Vladimir Bershov
Vladimir Bershov

Reputation: 2832

// 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.

Upvotes: 4

Zaiborg
Zaiborg

Reputation: 2522

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.

Upvotes: 0

Related Questions