qtasynchronousqwidgetqthreadqtconcurrent

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.


Solution

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