如何用异步获取的数据填充QTableView? [英] How do I populate a QTableView with asynchronous fetched data?

查看:104
本文介绍了如何用异步获取的数据填充QTableView?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想在创建QTableView时用一些提取的数据(例如,使用数据库或网络请求)填充QTableView.因为请求需要一些时间-从而阻塞了GUI-我得出的结论是使用另一个线程进行提取.

I want to populate a QTableView with some fetched data (using for example a database- or network-request) upon its creation. Because the request takes some time - thus blocking the GUI - I came to the conclusion to use another thread for fetching.

我当前的设置如下所示(显然已简化):

My current setup looks like this (obviously simplified):

class MyTable : public QTableView {
    QFutureWatcher<QAbstractItemModel*>* watcher;

    void init() {
        watcher = new QFutureWatcher<QAbstractItemModel*>();
        connect(watcher, SIGNAL(finished()), this, SLOT(onResult()));
        watcher->setFuture(QtConcurrent::run(...))
    }

    void onResult() {
        setModel(watcher->result());
    }
}

将对象添加到GUI之后,将调用init()方法.因为我对C ++/Qt/多线程还很陌生,所以我想问一下这段代码是否表现出预期的效果,或者是否会遇到某种竞争情况或类似情况.我特别担心onResult()方法,因为我担心"setModel"可能不是线程安全的.

The init()-methode is called after the object has been added to the GUI. Because I am quite new to C++ / Qt / Multithreading, I wanted to ask if this code behaves as expected or if I may run into some kind of race condition or the like. I'm especially concerned about the onResult()-method, because I fear "setModel" might not be thread safe.

推荐答案

如果模型是异步加载其数据的,则无需对视图进行子类化.这与视图的行为无关.

You don't need to subclass the view if the model is loading its data asynchronously. This has nothing to do with the view's behavior.

模型/视图的整个目的模式是将模型和视图组件分离,以提高灵活性和重用性.通过像这样对视图进行子类化,您将再次将它们耦合在一起.

The whole purpose of the Model/View pattern is to decouple the model and the view components to increase flexibility and reuse. By subclassing the view like that, You are coupling them again.

我特别担心onResult()方法,因为我担心"setModel"可能不是线程安全的.

I'm especially concerned about the onResult()-method, because I fear "setModel" might not be thread safe.

您是正确的setModel不是线程安全的.除了主线程外,您不应触摸其他任何QWidget,请参见文档.但是,可以确保在视图所在的线程中调用 onResult方法(应该是主线程).因此,这里没有错. .

You are right, setModel is not thread-safe. You shouldn't touch any QWidget from a thread other than the main thread, see docs. But, onResult method is guaranteed to be called in the thread where the view lives (that should be the main thread). So, there is nothing wrong here. . .

但是,似乎您是在从线程池调用的函数中创建模型.如果您不将模型移到函数末尾的主线程中(并且很可能您没有这样做),您的模型将生活在一个不运行事件循环的线程中.它将无法接收事件,这只是自找麻烦.通常,应避免在线程之间传递QObject(如果可能),而仅传递所需的数据结构.

But, it seems that you are creating the model in a function that is called from the thread pool. If you don't move the model to the main thread at the end of your function (and most likely you aren't doing that), your model will be living in a thread that doesn't run an event loop. It will not be able to receive events, this is just asking for trouble. Generally you should avoid passing QObjects between threads (when this is possible), and only pass data structures that you need.

我将从头开始,并通过子类化QAbstractTableModel来实现整个过程,这是一个完整的最小示例:

I'll start from scratch, and implement the whole thing by subclassing QAbstractTableModel, Here is a complete minimal example:

#include <QtWidgets>
#include <QtConcurrent>
#include <tuple>

class AsyncTableModel : public QAbstractTableModel{
    Q_OBJECT
    //type used to hold the model's internal data in the variable m_rows
    using RowsList = QList<std::tuple<QString, QString, QString> >;
    //model's data
    RowsList m_rows;
    QFutureWatcher<RowsList>* m_watcher;
public:
    explicit AsyncTableModel(QObject* parent= nullptr):QAbstractTableModel(parent){
        //start loading data in the thread pool as soon as the model is instantiated 
        m_watcher = new QFutureWatcher<RowsList>(this);
        connect(m_watcher, &QFutureWatcher<RowsList>::finished,
                this, &AsyncTableModel::updateData);
        QFuture<RowsList> future = QtConcurrent::run(&AsyncTableModel::retrieveData);
        m_watcher->setFuture(future);
    }
    ~AsyncTableModel() = default;

    //this is a heavy function that returns data you want the model to display
    //this is called in the thread pool using QtConcurrent::run
    static RowsList retrieveData(){
        //the function is heavy that it blocks the calling thread for 2 secs
        QThread::sleep(2);
        RowsList list;
        for(int i=0; i<10; i++){
            list.append(std::make_tuple(QString("A%0").arg(i),
                                        QString("B%0").arg(i),
                                        QString("C%0").arg(i)));
        }
        return list;
    }
    //this is the slot that is called when data is finished loading
    //it resets the model so that it displays new data
    Q_SLOT void updateData(){
        beginResetModel();
        m_rows = m_watcher->future().result();
        endResetModel();
    }

    int rowCount(const QModelIndex &parent) const {
        if(parent.isValid()) return 0;
        return m_rows.size(); 
    }
    int columnCount(const QModelIndex &parent) const {
        if(parent.isValid()) return 0;
        return 3; 
    }

    QVariant data(const QModelIndex &index, int role) const {
        QVariant value= QVariant();
        switch(role){
        case Qt::DisplayRole: case Qt::EditRole:
            switch(index.column()){
            case 0:
                value= std::get<0>(m_rows[index.row()]);
                break;
            case 1:
                value= std::get<1>(m_rows[index.row()]);
                break;
            case 2:
                value= std::get<2>(m_rows[index.row()]);
            }
            break;
        }
        return value;
    }
};

int main(int argc, char* argv[]){
    QApplication a(argc, argv);

    QTableView tv;
    AsyncTableModel model;
    tv.setModel(&model);
    tv.show();


    return a.exec();
}

#include "main.moc"


注意:

以上示例显示了如何从长时间阻塞线程的函数中将数据异步加载到模型中.执行大量计算的函数就是这种情况.如果您的目标是通过网络加载数据,则应使用 QTcpSocket / QNetworkAccessManager ,无需在所有情况下都使用线程池,但除此之外,所有内容都应该相似.


Note:

The example above shows how to load data from a function that blocks the thread for a long time into a model asynchronously. This is the case for functions that perform heavy computations. If your objective is to load data over the network, You should use the asynchronous API provided in QTcpSocket/QNetworkAccessManager, there is no need to use the thread pool in these cases at all, but other than that, everything should be similar.

这篇关于如何用异步获取的数据填充QTableView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆