使用带 GUI 的 QT 复制文件的正确方法避免冻结

Proper way to Copy files usign QT with GUI avoiding freeze

我有一个应用程序可以使用 QFile::Copy(..) 将文件从一个位置复制到另一个位置。复制过程在一个单独的线程中执行到一个工作对象中,但是,有时 GUI 冻结,我在这里阅读了很多关于这个的主题,但我明白这个方法(worker class) 是正确的。我在其他项目中采用相同的方法在另一个线程中执行进程,例如计时表,它工作得非常好,流畅。似乎只有在复制文件时才会发生这种小冻结。在 MS Windows 中,"lag" 比在 Linux 中更明显,在最后一个中,除非您复制一个大文件(700MB),否则不会检测到冻结,但只有当文件是结束复制,在复制过程中,GUI 是有响应的。我在 mainWindow (BUMain) class:

中使用了这种方法
void BUMain::initThreadSetup()
{
    thread = new QThread;
    Worker *worker = new Worker();
    worker->moveToThread(thread);

    connect(worker,SIGNAL(worker_Signal_updateProgressBar(int)),ui->progressBar,SLOT(setValue(int)),Qt::QueuedConnection);
    connect(this,SIGNAL(main_signal_copyFile(int,QStringList,QString)),worker,SLOT(worker_Slot_copyFile(int,QStringList,QString)),Qt::QueuedConnection);
    connect(worker,SIGNAL(worker_signal_keepCopying()),this,SLOT(main_slot_keepCopying()),Qt::QueuedConnection);
    connect(worker,SIGNAL(worker_signal_logInfo(QString)),gobLogViewer,SLOT(logger_slot_logInfo(QString)),Qt::QueuedConnection);

    connect(thread,SIGNAL(finished()),worker,SLOT(deleteLater()));
    connect(thread,SIGNAL(finished()),thread,SLOT(deleteLater()));
    thread->start();

}

在MainGui(BUMain)构造函数中调用此方法启动线程。一些相关代码:

这是发出 worker_signal_keepCopying() 时调用的插槽:

void BUMain::main_slot_keepCopying()
{
    giProgress ++;
    if(giProgress < giFileCounter){
        emit(main_signal_copyFile(giProgress,gobPaths,ui->toFilesTextField->text()));
    }
}

在这里,我进行了一次计数器验证并发出一个新信号以通知工作人员它可以继续下一份副本。复制过程是一个一个地完成的。在 worker.cpp 文件中,您可以看到插槽 worker_Slot_copyFile(int liIndex,QStringList files,QString path) 实现:

worker.cpp:

#include "worker.h"
#include <QFile>
#include <QFileInfo>
#include <QStringList>
#include <QCoreApplication>

Worker::Worker(QObject *parent) :
    QObject(parent)
{

}

void Worker::worker_Slot_copyFile(int liIndex,QStringList files,QString path)
{
    QString fileName;

    fileName = QFileInfo(files.at(liIndex)).baseName()+"."+QFileInfo(files.at(liIndex)).completeSuffix();

    //If the file exist, delete it
    if (QFile::exists(path+"/"+fileName))
    {
        QFile::remove(path+"/"+fileName);
    }

    QFile lobFile(files.at(liIndex));

    if(lobFile.copy(path+"/"+fileName)){

        //Write to a logger class
        emit(worker_signal_logInfo("File: " + fileName + " copied to: " + path));

        //Update a progress bar in the main GUI
        emit(worker_Signal_updateProgressBar(liIndex+1));
    }

    //The file has been processed!, I'm ready to copy another file...
    emit(worker_signal_keepCopying());

}

worker.h:

#ifndef WORKER_H
#define WORKER_H

#include <QObject>
#include <QThread>
#include <QStringList>

class Worker : public QObject
{
    Q_OBJECT
public:
    explicit Worker(QObject *parent = 0);

signals:
    void worker_Signal_updateProgressBar(int value);
    void worker_signal_keepCopying();
    void worker_signal_logInfo(QString info);

public slots:
    void worker_Slot_copyFile(int liIndex, QStringList files, QString path);

};

#endif // WORKER_H

所以,用文字来说,过程可以是:

好的!让我们开始复制一些文件!!请记住 BUMain 是 mainWindow class,其中 GUI 是 运行ning:

  1. BUMain 将进度条最大值调整为要复制的文件总数
  2. BUMain 将进度条值设置为 0
  3. BUMain 发出 main_signal_copyFile(...) 信号。这就像 "Hey worker! start copy a new file please, I will be here doing another stuff, and when you finish copying it, plase tell me."
  4. Worker 收到信号并调用 worker_Slot_copyFile。就像 "Hey BUMain, I can hear you, order received! Now I will copy the file"
  5. Worker 完成复制发出 worker_signal_keepCopying():"BUMain I have ended copying the file, I can copy another if you want, just notify me."
  6. BUMain 调用 main_slot_keepCopying():"I can hear you worker, Thanks!" 再次发出 main_signal_copyFile(...):"Worker, I have more files to copy, please copy another one".
  7. 重复该过程,直到处理完所有文件。

有时候,这个过程非常好,没有滞后或冻结,但有时却没有。请注意,此方法旨在避免阻塞。

我也试过 运行 worker class 中的 for 循环来复制所有文件而不通知 main class,但是延迟很严重并且 GUI 变得无响应复制大文件(> 300MB)时。例如:

void Worker::worker_Slot_copyFile(int liIndex,QStringList files,QString path)
{
    QString fileName;
    for(int liIndex = 0; liIndex < files.length() - 1; liIndex ++){
        fileName = QFileInfo(files.at(liIndex)).baseName()+"."+QFileInfo(files.at(liIndex)).completeSuffix();

        if (QFile::exists(path+"/"+fileName))
        {
            QFile::remove(path+"/"+fileName);
        }

        QFile lobFile(files.at(liIndex));

        if(lobFile.copy(path+"/"+fileName)){
            emit(worker_signal_logInfo("File: " + fileName + " copied to: " + path));
            emit(worker_Signal_updateProgressBar(liIndex+1));
        }
    }
}

希望能说清楚,这个有点难解释。我已将 this 作为工作人员 class 方法的参考。

注意:我使用 QT5.1.1 进行编程,使用 Windows 10,使用 Arch Linux 部署应用程序。

如有任何帮助或建议,我们将不胜感激。提前致谢,祝您有愉快的一天!

你的方法是正确的,虽然有点冗长。 "lag" 可能是由于内核对如何管理页面缓存和逐出应用程序的某些部分以便为正在复制的文件中的页面腾出空间的方式愚蠢。唯一的补救办法可能是使用特定于平台的文件复制机制,该机制通知内核您将按顺序读取和写入,并且在完成写入后您不再需要数据。参见 this answer for details and some Linux code. On Windows, one would hope that CopyFileEx does the correct advisory calls, a Qt example is here