非阻塞工作者 - 中断文件复制
Non-blocking worker - interrupt file copy
我正在处理非常大的文件,超过数百 GB。用户需要能够在磁盘之间移动这些文件,并且在没有默认文件管理器的受限系统上。用户有可能意识到他们犯了错误并取消操作,据我所知,用户将不得不等待当前的复制或重命名操作完成。这可能会让他们感到沮丧,因为他们可能会等待几分钟,却发现他们的许多 GB 文件仍然被复制。在复制的情况下,我可以删除第二个文件,但在重命名的情况下,我用来移动文件,我必须反向重复操作才能撤消它,这是不可接受的。
有没有什么方法可以中断我在 QFile 文档中没有看到的 copy() 和 rename(),或者我是否需要自己组合 class 来处理复制和重命名?
我认为您要查找的功能不存在。
您可以做的是不使用 copy() 函数,而是创建一个新文件并逐渐从旧文件读取 (qint64 maxSize) 到 QByteArray,然后写入 (const QByteArray & byteArray) 到新文件。
这样您就可以自己控制流程,只需检查用户是否没有在每个 read/write.
之间按下取消
我认为文件大小对重命名所需的时间没有任何影响。
对于副本 - Qt 没有提供任何内置功能,您必须自己实现它。这里的关键问题是你必须找到一些方法来连续轮询副本取消。这意味着您不能为了能够处理事件而锁定主线程。
无论您是选择一个额外的线程以保持主线程响应,还是决定使用主线程 - 在这两种情况下您都需要实施 "fragmented" 复制 - 一次一个块使用缓冲区,直到文件被复制或复制被取消。您需要它才能处理用户事件并跟踪复制进度。
我建议您实现一个 QObject
派生的复制助手 class 来跟踪文件名、总大小、缓冲区大小、进度并在取消时进行清理。那么到底是在主线程还是在专用线程中使用,就看你的选择了。
编辑:找到了,但你最好仔细检查一下,因为它是作为示例完成的,尚未经过彻底测试:
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 的进度。您也可以轻松调整它以移动文件。
我正在处理非常大的文件,超过数百 GB。用户需要能够在磁盘之间移动这些文件,并且在没有默认文件管理器的受限系统上。用户有可能意识到他们犯了错误并取消操作,据我所知,用户将不得不等待当前的复制或重命名操作完成。这可能会让他们感到沮丧,因为他们可能会等待几分钟,却发现他们的许多 GB 文件仍然被复制。在复制的情况下,我可以删除第二个文件,但在重命名的情况下,我用来移动文件,我必须反向重复操作才能撤消它,这是不可接受的。
有没有什么方法可以中断我在 QFile 文档中没有看到的 copy() 和 rename(),或者我是否需要自己组合 class 来处理复制和重命名?
我认为您要查找的功能不存在。
您可以做的是不使用 copy() 函数,而是创建一个新文件并逐渐从旧文件读取 (qint64 maxSize) 到 QByteArray,然后写入 (const QByteArray & byteArray) 到新文件。 这样您就可以自己控制流程,只需检查用户是否没有在每个 read/write.
之间按下取消我认为文件大小对重命名所需的时间没有任何影响。
对于副本 - Qt 没有提供任何内置功能,您必须自己实现它。这里的关键问题是你必须找到一些方法来连续轮询副本取消。这意味着您不能为了能够处理事件而锁定主线程。
无论您是选择一个额外的线程以保持主线程响应,还是决定使用主线程 - 在这两种情况下您都需要实施 "fragmented" 复制 - 一次一个块使用缓冲区,直到文件被复制或复制被取消。您需要它才能处理用户事件并跟踪复制进度。
我建议您实现一个 QObject
派生的复制助手 class 来跟踪文件名、总大小、缓冲区大小、进度并在取消时进行清理。那么到底是在主线程还是在专用线程中使用,就看你的选择了。
编辑:找到了,但你最好仔细检查一下,因为它是作为示例完成的,尚未经过彻底测试:
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 的进度。您也可以轻松调整它以移动文件。