【问题标题】:Non-blocking worker - interrupt file copy非阻塞工作者 - 中断文件复制
【发布时间】:2016-01-02 08:07:05
【问题描述】:

我正在处理非常大的文件,超过数百 GB。用户需要能够在磁盘之间移动这些文件,并且位于没有默认文件管理器的受限系统上。用户可能会意识到他们犯了错误并取消操作,据我所知,用户将不得不等待当前的复制或重命名操作完成。这可能会让他们感到沮丧,因为他们可能会等待几分钟,却发现他们的许多 GB 文件仍然被复制。在 Copy 的情况下,我可以删除第二个文件,但在我用来移动文件的 rename 的情况下,我必须反向重复操作才能撤消它,这是不可接受的。

有什么方法可以中断我在 QFile 的文档中没有看到的 copy() 和 rename(),还是我需要组合我自己的类来处理复制和重命名?

【问题讨论】:

    标签: c++ qt qt4.8 qfile


    【解决方案1】:

    我认为文件大小对重命名需要多长时间没有任何影响。

    对于副本 - Qt 没有提供任何内置功能,您必须自己实现它。这里的关键问题是您必须找到某种方法来不断地轮询副本取消。这意味着您不能锁定主线程以便能够处理事件。

    无论您是使用额外线程来保持主线程响应,还是决定使用主线程 - 在这两种情况下,您都需要实现“碎片化”复制 - 使用缓冲区一次一个块,直到文件被复制或复制被取消。您需要它才能处理用户事件并跟踪复制进度。

    我建议你实现一个QObject 派生的复制助手工作类,它跟踪文件名、总大小、缓冲区大小、进度和取消时的清理。那么是在主线程中使用还是在专用线程中使用是一个选择问题。

    编辑:找到了,但您最好仔细检查一下,因为它是作为示例完成的,尚未经过彻底测试:

    class CopyHelper : public QObject {
        Q_OBJECT
        Q_PROPERTY(qreal progress READ progress WRITE setProgress NOTIFY progressChanged)
    public:
        CopyHelper(QString sPath, QString dPath, quint64 bSize = 1024 * 1024) :
            isCancelled(false), bufferSize(bSize), prog(0.0), source(sPath), destination(dPath), position(0) { }
        ~CopyHelper() { free(buff); }
    
        qreal progress() const { return prog; }
        void setProgress(qreal p) {
            if (p != prog) {
                prog = p;
                emit progressChanged();
            }
        }
    
    public slots:
        void begin() {
            if (!source.open(QIODevice::ReadOnly)) {
                qDebug() << "could not open source, aborting";
                emit done();
                return;
            }
            fileSize = source.size();
            if (!destination.open(QIODevice::WriteOnly)) {
                qDebug() << "could not open destination, aborting";
                // maybe check for overwriting and ask to proceed
                emit done();
                return;
            }
            if (!destination.resize(fileSize)) {
                qDebug() << "could not resize, aborting";
                emit done();
                return;
            }
            buff = (char*)malloc(bufferSize);
            if (!buff) {
                qDebug() << "could not allocate buffer, aborting";
                emit done();
                return;
            }
            QMetaObject::invokeMethod(this, "step", Qt::QueuedConnection);
            //timer.start();
        }
        void step() {
            if (!isCancelled) {
                if (position < fileSize) {
                    quint64 chunk = fileSize - position;
                    quint64 l = chunk > bufferSize ? bufferSize : chunk;
                    source.read(buff, l);
                    destination.write(buff, l);
                    position += l;
                    source.seek(position);
                    destination.seek(position);
                    setProgress((qreal)position / fileSize);
                    //std::this_thread::sleep_for(std::chrono::milliseconds(100)); // for testing
                    QMetaObject::invokeMethod(this, "step", Qt::QueuedConnection);
                } else {
                    //qDebug() << timer.elapsed();
                    emit done();
                    return;
                }
            } else {
                if (!destination.remove()) qDebug() << "delete failed";
                emit done();
            }
        }
        void cancel() { isCancelled = true; }
    
    signals:
        void progressChanged();
        void done();
    
    private:
        bool isCancelled;
        quint64 bufferSize;
        qreal prog;
        QFile source, destination;
        quint64 fileSize, position;
        char * buff;
        //QElapsedTimer timer;
    };
    

    done() 信号用于deleteLater() 复制助手/关闭复制对话框等。您可以启用经过计时器并使用它来实现经过时间属性和估计时间。暂停是另一个可能实现的功能。使用QMetaObject::invokeMethod() 允许事件循环定期处理用户事件,以便您可以取消和更新从 0 到 1 的进度。您也可以轻松地调整它以移动文件。

    【讨论】:

    • 我害怕那个。只是几个小时我宁愿在别处度过。我会用你建议的模式做一些事情,它必须在另一个线程中以确保 GUI 可以随着进度更新。从好的方面来说,我可以通过这种方式提供文件特定的进度。
    • 至于rename(),我用它来移动文件。当您移动到同一个磁盘上的另一个目录时,它是不变的,但是从一个磁盘移动到另一个磁盘确实需要时间,具体取决于文件大小。
    • @MildWolfie - 我想我已经做过类似的事情了,我会在几个小时后回家看看。
    • 这就是我最终为了保持 UI 响应而做的事情。
    • @MildWolfie - 找到它,可能会为您节省一些时间。
    【解决方案2】:

    我认为您要查找的功能不存在。

    您可以做的不是使用 copy() 函数,而是创建一个新文件并从旧文件逐渐读取 (qint64 maxSize) 到 QByteArray 并将 (const QByteArray & byteArray) 写入新文件。 这样您就可以自己控制流程,只需检查用户是否在每次读/写之间都没有按下取消键。

    【讨论】:

      猜你喜欢
      • 2021-12-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-19
      • 1970-01-01
      • 2012-12-24
      • 1970-01-01
      • 2015-07-19
      相关资源
      最近更新 更多