非阻塞工作者 - 中断文件副本 [英] Non-blocking worker - interrupt file copy

查看:126
本文介绍了非阻塞工作者 - 中断文件副本的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

有没有办法中断copy()和rename(),我在QFile的文档中没有看到,或者我需要把我自己的类来处理复制和重命名?

解决方案

我不认为文件大小对重命名需要多长时间。 p>

对于复制 - Qt不提供任何内置,你必须自己实现。这里的关键是,你将不得不找到一些方法来连续轮询取消复印。这意味着您不能锁定主线程以便能够处理事件。



无论您是为了保持主线程响应,或者决定使用主线程 - 在这两种情况下,您将需要使用缓冲区一次实现分段复制 - 一个块,直到复制文件或复制被取消。



我建议您实现一个 QObject 派生复制助手工作类,它跟踪文件名,总大小,缓冲区大小,进度和取消清理。然后,它是一个选择是否将使用它在主线程或在一个专用的线程。



编辑:找到它,但你最好仔细检查,因为它是作为一个例子,并没有被彻底测试:

  class CopyHelper:public QObject {
Q_OBJECT
Q_PROPERTY(qreal progress READ READ 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 }

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()<< 无法开源,中止;
emit done();
return;
}
fileSize = source.size();
if(!destination.open(QIODevice :: WriteOnly)){
qDebug()< 无法打开目的地,中止;
//可能检查覆盖并要求继续
emit done();
return;
}
if(!destination.resize(fileSize)){
qDebug()< 不能调整大小,中止;
emit done();
return;
}
buff =(char *)malloc(bufferSize);
if(!buff){
qDebug()< 无法分配缓冲区,中止;
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。您可以轻松地调整移动文件。


I'm dealing with very large files, in excess of hundreds of GB in size. The User needs to be able to move these files between disks and is on a restricted system with no default file manager. It's possible for the User to realize they've made a mistake and cancel the operation, and as far as I can tell the User will have to wait for the current copy or rename operation to complete. This can leave them feeling frustrated as they wait potentially minutes, only to see that their many GB file still copied. In the case of Copy I could delete the second file, but in the case of rename, which I'm using to move files, I'd have to repeat the operation in reverse to undo it, and that's simply not acceptable.

Is there some way to interrupt copy() and rename() that I'm not seeing in the documentation for QFile, or will I need to put together my own class to handle copy and rename?

解决方案

I don't think the file size has any effect on how long a renaming will take.

For the copy - Qt offers nothing built in, you have to implement it yourself. The key gotcha here is that you will have to find some way to poll for a copy cancellation continuously. This means you cannot lock the main thread in order to be able to process events.

Whether you go for an extra thread in order to keep the main thread responsive, or decide to use the main thread - in both cases you will need to implement "fragmented" copying - one chunk at a time using a buffer, until the file is copied or copying is cancelled. You need this to be able to process user events and track copying progress.

I suggest you implement a QObject derived copy helper worker class which tracks file name, total size, buffer size, progress and clean up on cancellation. Then it is a matter of choice whether you will use it in the main thread or in a dedicated thread.

EDIT: Found it, but you better double check it, since it was done as an example and has not been thoroughly tested:

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;
};

The done() signal is used to deleteLater() the copy helper / close copy dialog or whatever. You can enable the elapsed timer and use it to implement an elapsed time property and estimated time as well. Pausing is another possible feature to implement. Using QMetaObject::invokeMethod() allows the event loop to periodically process user events so you can cancel and update progress, which goes from 0 to 1. You can easily tweak it for moving files as well.

这篇关于非阻塞工作者 - 中断文件副本的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆