• 6616阅读
  • 11回复

使用Model/View编程加载大量数据的正确姿势 [复制链接]

上一主题 下一主题
离线yyzq
 

只看楼主 倒序阅读 楼主  发表于: 2018-08-31
先放结论(欢迎大家去验证完善指正):
如果没有使用代理模型,建议将视图的setModel放在模型加载完数据后,如果是放在之前,建议在加载数据之前先禁止视图刷新,即setUpdatesEnabled(false),加载完数据后再恢复。
如果使用了代理模型,一定要将代理模型的setSourceModel放在model加载完数据后,否则耗时简直是不可思议。


下面开始吧啦吧啦。。。

最近项目中需要一次性加载大量的数据,于是就采用了模型视图去做,但是实测的时候发现加载1000条数据要20秒左右,简直不可思议,从来没有遇到过这种问题。当然了,以前都是使用分批加载数据,没有一次性加载的需求,就没发现过这种问题,于是就开始了耗时排查之旅。



首先想到使用线程去做,在线程中向模型添加数据,但是程序运行起来后,发现视图上没数据,查找发现是模型的setChild函数出了警告,报出QVector<int>和QList<QPersistentModelIndex>不是meataData,要我去注册,最后整了一通发现行不通。当然了即便是可以的,耗时并不会减少,也只是不会让界面冻结,并没有真正解决问题,所以就放弃了这条路,还是得找出耗时的问题在哪。


接下来又是一通查找,发现是随着循环的进行,setChild越来越慢,这是什么鬼。虽然我没有去看源码,想来这个函数大概是在组织数据结构等,怎么会越来越慢呢,而且是慢的非常明显,时间都耗在这了。再进一步排查,摆出各种姿势,看文档,搜百度(懒得去翻墙,各种被禁,费劲)都没有结果,于是回来再去梳理代码,发现没有什么问题,以前都是这么用的啊。没办法,这问题得解决,都卡了四天了,脸上都起了好几个痘痘了,上火。。。


最终经过我的不懈努力,发现是使用了QSortFileterProxyModel的原因(我是继承该类实现了自己的过滤排序规则),把它去掉后瞬间降到毫秒级,这才是正确的。但是不能不用这个代理模型,于是我又是一通尝试,发现只要将视图的setModel放在模型加载完数据后就没问题了。然后我又去验证断网重连后是否还有效(断网重连后会清空模型重新加载),发现又不行了,心拔凉拔凉的。。。

又是一通查找,发现只要将代理模型的setSourceModel放在model加载完数据后就没事了。至此问题找到了,我没有去看源码,为什么会造成这种现象,欢迎大神给解释解释。下面是我经过优化的代码:


  1. void BTUsrCtlWidget::initDeviceInfo()
  2. {
  3.     // 清空模型
  4.     //
  5.     mDeviceModel->clear();
  6.     mDeviceView->clearSelection();
  7.     mDeviceProxyModel->setSourceModel(nullptr);
  8.     QItemSelectionModel *selectionModel = mDeviceView->selectionModel();
  9.     mDeviceView->setModel(nullptr);
  10.     if (selectionModel != nullptr)
  11.     {
  12.         if (selectionModel->parent() != nullptr)
  13.         {
  14.             selectionModel->deleteLater();
  15.         }
  16.         else
  17.         {
  18.             delete selectionModel;
  19.             selectionModel = nullptr;
  20.         }
  21.     }
  22.     
  23.     mDeviceView->setUpdatesEnabled(false);
  24.     // 分类信息
  25.     //
  26.     QStringList deviceTypeList;
  27.     deviceTypeList << tr("Intercom Device") << tr("Video Device") << tr("VoIP")
  28.         << tr("Broadcast Device") << tr("Monitor Device") << tr("Other Device");
  29.     for (int i = 0; i < deviceTypeList.size(); i++)
  30.     {
  31.         QStandardItem *deviceTypeItem = new QStandardItem(deviceTypeList.at(i));
  32.         deviceTypeItem->setForeground(QBrush(QColor(190, 197, 202)));
  33.         deviceTypeItem->setData(ItemMark::MARK_DEVICE, Qt::UserRole);
  34.         deviceTypeItem->setData(i+1, USER_ROLE_DEVICE_TYPE);
  35.         deviceTypeItem->setToolTip(deviceTypeList.at(i));
  36.         QStandardItem *deviceTypeCheckItem = new QStandardItem();
  37.         deviceTypeCheckItem->setCheckable(true);
  38.         mDeviceModel->setItem(i, 0, deviceTypeItem);
  39.         mDeviceModel->setItem(i, 1, deviceTypeCheckItem);
  40.     }
  41.     // 加载数据
  42.     //
  43.     foreach (DepartmentUserInfo info, PttHelper.mDepartmentUserInfoList)
  44.     {
  45.         QStandardItem *deviceUserItem = new QStandardItem();
  46.         // save data
  47.         //
  48.         QVariant var;
  49.         var.setValue(info);
  50.         deviceUserItem->setData(ItemMark::MARK_MEMBER, Qt::UserRole);
  51.         deviceUserItem->setData(var, USER_ROLE_DEVICE_USER);
  52.         // set foreground and icon
  53.         //
  54.         if (info.online == Offline)
  55.         {
  56.             deviceUserItem->setForeground(QBrush(QColor(88, 96, 106)));                    
  57.             deviceUserItem->setIcon(AppHelper.getUserIcon(false, info.type));
  58.         }
  59.         else
  60.         {
  61.             deviceUserItem->setForeground(QBrush(QColor(190, 197, 202)));                    
  62.             deviceUserItem->setIcon(AppHelper.getUserIcon(true, info.type));
  63.         }
  64.         // set user name
  65.         //
  66.         QString displayName = AppHelper.displayName(info.name, info.account);
  67.         deviceUserItem->setText(displayName);
  68.         deviceUserItem->setToolTip(displayName);
  69.         // setCheckState
  70.         //
  71.         QStandardItem *deviceUserCheckItem = new QStandardItem();
  72.         deviceUserCheckItem->setCheckable(true);
  73.         // add user to tree
  74.         //
  75.         for (int i = 0; i < mDeviceModel->rowCount(); i++)
  76.         {
  77.             QStandardItem *deviceTypeItem = mDeviceModel->item(i, 0);
  78.             if ((deviceTypeItem != nullptr) && (deviceTypeItem->data(USER_ROLE_DEVICE_TYPE).toInt() == info.type))
  79.             {
  80.                 int rowNumber = deviceTypeItem->rowCount();
  81.                 deviceTypeItem->setChild(rowNumber, 0, deviceUserItem);
  82.                 deviceTypeItem->setChild(rowNumber, 1, deviceUserCheckItem);
  83.                 break;
  84.             }
  85.         }
  86.         QApplication::processEvents();
  87.     }
  88.     mDeviceProxyModel->setSourceModel(mDeviceModel);
  89.     mDeviceView->setModel(mDeviceProxyModel);
  90.     mDeviceModel->setColumnCount(2);
  91.     mDeviceView->setColumnWidth(0, 205);
  92.     mDeviceView->setUpdatesEnabled(true);
  93. }



离线九重水

只看该作者 1楼 发表于: 2018-09-05
虽然没怎么用过MODEL/VIEW,但支持分享。
离线ch781609892

只看该作者 2楼 发表于: 2018-09-21
更新数据前先指定行数:setRowCount
离线stlcours

只看该作者 3楼 发表于: 2018-09-26
非常棒的文章,感谢楼主!
离线hehui

只看该作者 4楼 发表于: 2018-12-07
model的变化会触发VIEW的更新,大量的数据更新界面肯定会卡死
自己重新实现MODEL控制就可以了,最主要的是类似beginInsertRows这样的函数的调用时机
离线richards

只看该作者 5楼 发表于: 2018-12-08
我想搞明白 比如我有一个 大数据库需要加载时候   用modeleview模式 如何来搞 分批加载。比如还有查询的时候
离线yyzq

只看该作者 6楼 发表于: 2018-12-28
回 richards 的帖子
richards:我想搞明白 比如我有一个 大数据库需要加载时候   用modeleview模式 如何来搞 分批加载。比如还有查询的时候 (2018-12-08 16:19) 

仿照QQ或微信哪种效果就可以啊。。。
离线futureq

只看该作者 7楼 发表于: 2019-01-19
可能是因为model的变化时重新创建QModelIndex导致慢的
离线273406790

只看该作者 8楼 发表于: 2019-01-31
谢谢分享,尽管不知所云,哈哈,对于网络问题,应需一个timeout。
离线yanhuaw

只看该作者 9楼 发表于: 2020-04-10
和1楼一样,支持
离线big_mouse

只看该作者 10楼 发表于: 2020-04-10
离线liuyuanan

只看该作者 11楼 发表于: 2021-05-18
应该使用分批加载数据,详情请查询

    bool canFetchMore(const QModelIndex &parent) const;
    void fetchMore(const QModelIndex &parent);
快速回复
限100 字节
 
上一个 下一个