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());
}
}
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.
I'm especially concerned about the onResult()-method, because I fear "setModel" might not be thread safe.
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. . .
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 QObject
s between threads (when this is possible), and only pass data structures that you need.
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"
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With