如何在Qt对话框关闭时终止异步函数。

3

背景

我有一个对话框,在初始化时运行一个耗时操作。为了不冻结GUI,我将这个操作封装成了一个异步函数。

示例

想象一下一个对话框/小部件,它从远程服务器异步获取当前天气:

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

问题

如果在异步操作完成之前关闭此对话框/小部件,label->setText() 部分将会导致崩溃,因为此时该小部件对象不存在。

问题

如何正确处理这种情况? 可能我应该使用其他东西来代替QtConcurrent(例如QThread),以便在关闭对话框时可以正确取消异步函数。

注意

请注意,实际代码涉及读取一堆文件,与网络无关,因此使用异步的QNetworkRequest接口不适用。


2
我认为QtConcurrent::run()不适合你的方法,因为Qt文档中说:“QtConcurrent::run()不支持取消、暂停或进度报告...”。你可以使用QThread或者防止你的对话框关闭。 - vahancho
2个回答

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()));
}

同样也可以将与完成信号相关的所有连接断开:

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

附注:关于取消异步操作,QtConcurrentQThread方法无法正确取消。

QThread::terminate()方法存在,但从文档中可以得知:

警告:此函数存在危险,不建议使用。线程可以在其代码路径的任何点上被终止...

因此您必须在getWeather()函数内部实现一些“取消”标志,或按照上述方法进行处理。


0

QtConcurrent::run() 会返回一个 QFuture<T>。你可以在它上面调用 QFuture::waitForFinished

另一件事是,你不应该从另一个线程中调用 QLabel::setText,而应该使用 QMetaObject::invokeMethod 或者发射一个信号。


QFuture::waitForFinished 这个函数不是违背了在 Background 部分中描述的防止 GUI 冻结的初始想法吗?关于你的第二条评论,我会检查一下,谢谢。 - John Doe
不是百分之百确定,但当程序退出时,用户将不会注意到隐藏窗口中的冻结。我会等待未来完成当应用程序退出或当你的小部件dtor被调用。 - Zaiborg
很遗憾,这不是一个主窗口,而只是一个对话框。关闭它并不会退出应用程序,因此 QFuture::waitForFinished 将阻塞 GUI。 - John Doe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接