我想在 QTableView 创建时用一些获取的数据(例如使用数据库或网络请求)填充 QTableView。因为请求需要一些时间 - 因此阻塞了 GUI - 我得出的结论是使用另一个线程来获取。
我当前的设置如下所示(显然已简化):
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”可能不是线程安全的。
最佳答案
如果模型异步加载其数据,则无需子类化 View 。这与 View 的行为无关。
Model/View的全部目的模式是解耦模型和 View 组件以增加灵 active 和重用性。通过像这样对 View 进行子类化,您再次将它们耦合。
I'm especially concerned about the onResult()-method, because I fear "setModel" might not be thread safe.
你是对的,setModel
不是线程安全的。你不应该从主线程以外的线程中触摸任何 QWidget
,参见 docs .但是,onResult
方法保证在 View 所在的线程中调用(应该是主线程)。所以,这里没有错。 . .
但是,您似乎是在从线程池调用的函数中创建模型。如果您不在函数末尾将模型移动到主线程(很可能你没有这样做),你的模型将生活在一个不运行事件循环的线程中。它将无法接收事件,这只是自找麻烦。通常,您应该避免在线程之间传递 QObject
(如果可能),并且只传递您需要的数据结构。
我将从头开始,通过继承 QAbstractTableModel
来实现整个事情,这是一个完整的最小示例:
#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
中提供的异步 API/QNetworkAccessManager
,在这些情况下根本不需要使用线程池,但除此之外,一切都应该是相似的。
关于c++ - 如何使用异步获取的数据填充 QTableView?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42728602/