一叶知秋 发表于 2018-8-31 13:40:35

使用Model/View编程加载大量数据的正确姿势

本帖最后由 一叶知秋 于 2018-8-31 15:00 编辑

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


下面开始吧啦吧啦。。。


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

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

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

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


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

void BTUsrCtlWidget::initDeviceInfo()
{
    // 清空模型
    //
    mDeviceModel->clear();
    mDeviceView->clearSelection();
    mDeviceProxyModel->setSourceModel(nullptr);
    QItemSelectionModel *selectionModel = mDeviceView->selectionModel();
    mDeviceView->setModel(nullptr);
    if (selectionModel != nullptr)
    {
      if (selectionModel->parent() != nullptr)
      {
            selectionModel->deleteLater();
      }
      else
      {
            delete selectionModel;
            selectionModel = nullptr;
      }
    }
   
    mDeviceView->setUpdatesEnabled(false);

    // 分类信息
    //
    QStringList deviceTypeList;
    deviceTypeList << tr("Intercom Device") << tr("Video Device") << tr("VoIP")
      << tr("Broadcast Device") << tr("Monitor Device") << tr("Other Device");

    for (int i = 0; i < deviceTypeList.size(); i++)
    {
      QStandardItem *deviceTypeItem = new QStandardItem(deviceTypeList.at(i));
      deviceTypeItem->setForeground(QBrush(QColor(190, 197, 202)));
      deviceTypeItem->setData(ItemMark::MARK_DEVICE, Qt::UserRole);
      deviceTypeItem->setData(i+1, USER_ROLE_DEVICE_TYPE);
      deviceTypeItem->setToolTip(deviceTypeList.at(i));

      QStandardItem *deviceTypeCheckItem = new QStandardItem();
      deviceTypeCheckItem->setCheckable(true);

      mDeviceModel->setItem(i, 0, deviceTypeItem);
      mDeviceModel->setItem(i, 1, deviceTypeCheckItem);
    }

    // 加载数据
    //
    foreach (DepartmentUserInfo info, PttHelper.mDepartmentUserInfoList)
    {
      QStandardItem *deviceUserItem = new QStandardItem();

      // save data
      //
      QVariant var;
      var.setValue(info);
      deviceUserItem->setData(ItemMark::MARK_MEMBER, Qt::UserRole);
      deviceUserItem->setData(var, USER_ROLE_DEVICE_USER);

      // set foreground and icon
      //
      if (info.online == Offline)
      {
            deviceUserItem->setForeground(QBrush(QColor(88, 96, 106)));                  
            deviceUserItem->setIcon(AppHelper.getUserIcon(false, info.type));
      }
      else
      {
            deviceUserItem->setForeground(QBrush(QColor(190, 197, 202)));                  
            deviceUserItem->setIcon(AppHelper.getUserIcon(true, info.type));
      }

      // set user name
      //
      QString displayName = AppHelper.displayName(info.name, info.account);
      deviceUserItem->setText(displayName);
      deviceUserItem->setToolTip(displayName);

      // setCheckState
      //
      QStandardItem *deviceUserCheckItem = new QStandardItem();
      deviceUserCheckItem->setCheckable(true);

      // add user to tree
      //
      for (int i = 0; i < mDeviceModel->rowCount(); i++)
      {
            QStandardItem *deviceTypeItem = mDeviceModel->item(i, 0);
            if ((deviceTypeItem != nullptr) && (deviceTypeItem->data(USER_ROLE_DEVICE_TYPE).toInt() == info.type))
            {
                int rowNumber = deviceTypeItem->rowCount();
                deviceTypeItem->setChild(rowNumber, 0, deviceUserItem);
                deviceTypeItem->setChild(rowNumber, 1, deviceUserCheckItem);

                break;
            }
      }

      QApplication::processEvents();
    }

    mDeviceProxyModel->setSourceModel(mDeviceModel);
    mDeviceView->setModel(mDeviceProxyModel);
    mDeviceModel->setColumnCount(2);
    mDeviceView->setColumnWidth(0, 205);
    mDeviceView->setUpdatesEnabled(true);
}





一叶知秋 发表于 2018-8-31 13:45:01

为什么代码里加红不给我显示。。。。。。。重点是 清空模型和101、102行

张飞Dear 发表于 2018-8-31 13:58:58

赞~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

cj123sn 发表于 2018-8-31 15:06:52

可以的,下次可以用到mark

alexyuyu 发表于 2018-9-6 14:45:40

新手,默默学习。大神求带:lol

2273431064 发表于 2018-9-7 10:14:41


新手,默默学习。大神求带

richards 发表于 2019-8-7 23:57:49

有没有试过 QML 中使用自定义model 加载大量数据呢

一叶知秋 发表于 2019-8-8 09:26:45

richards 发表于 2019-8-7 23:57
有没有试过 QML 中使用自定义model 加载大量数据呢

没有
页: [1]
查看完整版本: 使用Model/View编程加载大量数据的正确姿势