• 850阅读
  • 4回复

[讨论]QFileSystemWatcher监控某个目录下千万级文件变化时导致主界面卡顿的问题 [复制链接]

上一主题 下一主题
离线lwei24
 

只看楼主 倒序阅读 楼主  发表于: 2023-04-12
如题,用QFileSystemWatcher监控某个指定的目录变化,若拷贝1000张图片到该监控目录,则会出现QFileSystemWatcher对象一下子触发很多个同一信号,这个信号对应一个同一槽函数,槽函数主要处理该信号获取的图片文件信息集合并发送给主线程,开启子线程,将图片信息同步更新到QTableView表格的模型上,具体代码如下:
  1. //CustomTableView.h
  2. class CustomTableView : public QTableView
  3. {
  4.     Q_OBJECT
  5. public:
  6.     CustomTableView(QWidget *parent = nullptr);
  7.     void addWatchPath(QString path);
  8.     
  9. signals:
  10.     void startUpdateDirectory(int type, QString diskPath, QList<FileInfo> recordList, QStringList curFileNameList, QString newFileName);
  11.     
  12. private:
  13.     QThread                      *m_fsThread;
  14.     FSWatcherWorker              *m_fsWorker;         //继承QObject
  15.     QFileSystemWatcher           *m_fsWatcher;
  16.     CustomTableModel             *m_customTableModel; //自定义模型,继承QAbstractTableModel
  17.     QMap<QString, QStringList> m_currentContentsMap; // 当前每个监控的内容目录列表
  18.     QList<FileInfo>                  m_recordList;         //监控目录的集合,FileInfo为监控目录下文件或文件夹的信息,例如名称、大小、修改时间等。
  19. };
  20. //CustomTableView.cpp
  21. CustomTableView::CustomTableView(QWidget *parent)
  22. :QTableView(parent)
  23. {this->addWatchPath("E:\\test\\");}
  24. void CustomTableView::addWatchPath(QString path)
  25. {
  26.     m_fsWatcher = new QFileSystemWatcher();
  27.     connect(m_fsWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(onDirectoryChanged(QString)));
  28.     // 添加监控路径
  29.     m_fsWatcher->addPath(path);
  30.     // 如果添加路径是一个目录,保存当前内容列表
  31.     QFileInfo file(path);
  32.     if (file.isDir())
  33.     {
  34.         const QDir dirw(path);
  35.         m_currentContentsMap[path] = dirw.entryList(QDir::NoDotAndDotDot | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
  36.     }
  37.     m_fsThread = new QThread(this);
  38.     if(m_fsWorker == NULL)
  39.     {
  40.         m_fsWorker = new FSWatcherWorker;
  41.         m_fsWorker->moveToThread(m_fsThread);
  42.         connect(m_fsThread, &QThread::finished, m_fsWorker, &FSWatcherWorker::deleteLater);
  43.         connect(m_fsThread, &QThread::finished, this, &CustomTableView::setFsWorkerNull);
  44.         connect(m_fsWorker, &FSWatcherWorker::updateDir, this, &CustomTableView::onUpdateDir);
  45.         connect(this, &CustomTableView::startUpdateDirectory, m_fsWorker, &FSWatcherWorker::startFSWatcherWork);//startFSWatcherWork获取文件信息并发送更新集合
  46.         connect(this, &CustomTableView::cancelUpdateDirectory, m_fsWorker, &FSWatcherWorker::cancelFSWatcherWork);
  47.         m_fsThread->start();
  48.     }
  49. }
  50. void dCustomTableView::onDirectoryChanged(QString path)
  51. {
  52.     // 比较最新的内容和保存的内容找出区别(变化)
  53.     QStringList currEntryList = m_currentContentsMap[path];
  54.     const QDir dir(path);
  55.     QStringList newEntryList = dir.entryList(QDir::NoDotAndDotDot  | QDir::AllDirs | QDir::Files, QDir::DirsFirst);
  56.     QSet<QString> newDirSet = QSet<QString>::fromList(newEntryList);
  57.     QSet<QString> currentDirSet = QSet<QString>::fromList(currEntryList);
  58.     // 添加了文件
  59.     QSet<QString> newFiles = newDirSet - currentDirSet;
  60.     QStringList newFile = newFiles.toList();
  61.     // 文件已被移除
  62.     QSet<QString> deletedFiles = currentDirSet - newDirSet;
  63.     QStringList deleteFile = deletedFiles.toList();
  64.     // 更新当前设置
  65.     m_currentContentsMap[path] = newEntryList;
  66.     if (!newFile.isEmpty() && !deleteFile.isEmpty())
  67.     {
  68.         // 文件/目录重命名
  69.         if ((newFile.count() == 1) && (deleteFile.count() == 1))
  70.         {
  71.             DEBUG_PRINT(FALSE, QString("directory updated: %1, File Renamed from %2 to %3").arg(path).arg(deleteFile.first()).arg(newFile.first()));
  72.             emit startUpdateDirectory(0, m_watchPath, currEntryList, newEntryList);
  73.         }
  74.     }
  75.     else
  76.     {
  77.         // 添加新文件/目录至Dir
  78.         if (!newFile.isEmpty())
  79.         {
  80.             DEBUG_PRINT(FALSE, QString("directory updated: %1, New Files/Dirs added: %2").arg(path).arg(QString::number(newFile.size())));
  81.             foreach (QString file, newFile)
  82.             {
  83.                 // 处理操作每个新文件....
  84.                 if(!file.isEmpty())
  85.                 {
  86.                     emit startUpdateDirectory(1, m_watchPath, m_recordList, currEntryList, file);
  87.                 }
  88.             }
  89.         }
  90.         // 从Dir中删除文件/目录
  91.         if (!deleteFile.isEmpty())
  92.         {
  93.             DEBUG_PRINT(FALSE, QString("directory updated: %1, Files/Dirs deleted: %2").arg(path).arg(QString::number(deleteFile.size())));
  94.             foreach(QString file, deleteFile)
  95.             {
  96.                 // 处理操作每个被删除的文件....
  97.                 if(!file.isEmpty())
  98.                 {
  99.                     emit startUpdateDirectory(-1, m_watchPath, m_recordList, currEntryList,file);
  100.                 }
  101.             }
  102.             
  103.         }
  104.     }
  105. }
  106. void dCustomTableView::onUpdateDir2(int size, FileRecord record)
  107. {
  108.     if(size > 0)
  109.     {
  110.         m_recordList.push_back(record);
  111.         m_customTableModel->updateData(m_recordList);        //此处更新模型数据,即具体updateData代码为{this->beginResetModel();m_recordList = recordList;this->endResetModel();}
  112.     }
  113.     if(size == 0)
  114.     {
  115.         m_recordList.clear();
  116.         m_customTableModel->removeRows(0, m_customTableModel->rowCount());
  117.         m_customTableModel->updateData(m_recordList);
  118.     }
  119. }
如上代码,在运行过程中,拷贝1000个文件到指定监控目录下,主界面从拷贝的那一刻开始到刷新QTableView表格上的模型的时间很卡,其中这段时间,将1000张照片完全拷贝完到监控目录大概时间40s到60s左右,而刷新QTableView模型就大概8分钟到10分钟左右,这个是测试的日志分析的。各位大佬们,你们有什么办法可以优化这个问题吗?在线等,欢迎各位大佬们指点一二,小弟感激不尽。




离线20091001753

只看该作者 1楼 发表于: 2023-04-12
qt 界面的绘制就在主线程,你想界面不卡也很简单,把密集型任务不要放到主线程处理,或者说,尽可能的不要把业务放在主线程处理。

例如你可以将 QFileSystemWatcher 放在其他线程。

其次,你可以设置缓冲,例如目录更新后,你就记载这个目录更新了。但是真正对目标目录处理的函数,只会每秒处理一次。

因为比如你复制很多小文件,这些文件可能1秒内复制了几百个,那你执行几百次更新目录犯不着,每秒处理一次就好。

甚至,你可以等改文件夹1秒内没有新的动作(稳定后)再更新目录。这适合于不是每时每刻都在更新的文件夹,而一旦他在更新,等他更新结束再去处理就好。

而且这个更新的步骤,例如涉及到文件的复制粘贴,也可以是放在其他线程的。
(づ ̄ 3 ̄)づ
离线lwei24

只看该作者 2楼 发表于: 2023-04-12
回 20091001753 的帖子
20091001753:qt 界面的绘制就在主线程,你想界面不卡也很简单,把密集型任务不要放到主线程处理,或者说,尽可能的不要把业务放在主线程处理。
例如你可以将 QFileSystemWatcher 放在其他线程。
其次,你可以设置缓冲,例如目录更新后,你就记载这个目录更新了。但是真正对目标目录处理的函 .. (2023-04-12 12:10)

多谢版主,您说的将 QFileSystemWatcher 放到子线程,我试试看。还有就是设置缓冲,我是否可以理解成延时等待。至于您说的等1s目录没有新动作再更新目录,这个可以有,只是等它更新结束再去处理,可能有些难,因为QFileSystemWatcher这个类好像没有提供关于任何检测更新动作是否结束的标志或接口,单单从延时等待1秒没有新动作去判断,恐怕有点难判断。
离线20091001753

只看该作者 3楼 发表于: 2023-04-12
QHash<QString,QDateTime> map,分别是目录与更新时间

directoryChanged 的信号:map[dir]=QDateTime::currentDateTime();
这样你就构建了一个有变化的目录,及该目录最后的更新时间,精确到毫秒的。

然后创建一个定时器,每秒检查这个 map
auto cur = QDateTime::currentDateTime();//获取最新的时间
for(auto& [k,v]:map)//检查 map,这个需要c17支持,然后对比 cur 与 v 相差超过1秒或多秒就执行更新,更新后把这个目录从 map 里删掉就好。

只要思想不滑坡,办法总比困难多。
(づ ̄ 3 ̄)づ
离线lwei24

只看该作者 4楼 发表于: 2023-04-12
回 20091001753 的帖子
20091001753:QHash<QString,QDateTime> map,分别是目录与更新时间
directoryChanged 的信号:map[dir]=QDateTime::currentDateTime();
这样你就构建了一个有变化的目录,及该目录最后的更新时间,精确到毫秒的。
....... (2023-04-12 14:41) 

哈哈哈,多谢版主,我考虑到拷贝文件的大小不确定,所以觉得拷贝单个文件的时间就不确定,姑卡在不知道用几秒执行更新目录合适,不过您说思路我会好好想想再试试看效果的。
快速回复
限100 字节
 
上一个 下一个