Правильный способ копирования файлов, используя QT с графическим интерфейсом пользователя, избегая зависания
У меня есть приложение, которое копирует файлы из одного места в другое, используя QFile::Copy(..)
, Процесс копирования выполняется в отдельном потоке в рабочий объект, однако, иногда графический интерфейс пользователя зависает, я читал здесь много тем об этом, но я понимаю, что этот метод (рабочий класс) является правильным. У меня есть такой же подход в других проектах, чтобы выполнить процесс в другом потоке, например, в хронометре, и он работает очень хорошо, плавно. Кажется, что эта небольшая остановка происходит только при копировании файлов. В MS Windows "отставание" более заметно, чем в Linux, в последнем случае замораживания не обнаруживаются, если только вы не копируете большой файл (700 МБ), но только когда файл заканчивается копированием, во время копирования - GUI отзывчивый Я использую этот подход в моем mainWindow (BUMain)
учебный класс:
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)
реализация:
Рабочий.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, в котором работает GUI:
- BUMain настроить максимум индикатора выполнения на общее количество файлов для копирования
- BUMain установить значение индикатора выполнения на 0
- BUMain испускают
main_signal_copyFile(...)
сигнал. Это как "Эй, рабочий! Начни копировать новый файл, пожалуйста, я буду здесь делать что-то еще, а когда ты закончишь копировать, скажи мне". - Работник получает сигнал и вызывает
worker_Slot_copyFile
, Это как "Эй, BUMain, я тебя слышу, заказ получен! Теперь я скопирую файл" - Работник заканчивает излучение копии
worker_signal_keepCopying()
: "BUMain Я закончил копировать файл, я могу скопировать другой, если хотите, просто сообщите мне." - BUMain вызывает
main_slot_keepCopying()
: "Слышу, рабочий, спасибо!" излучаетmain_signal_copyFile(...)
еще раз: "Работник, у меня есть еще файлы для копирования, пожалуйста, скопируйте еще один". - Процесс повторяется до тех пор, пока все файлы не будут обработаны.
Иногда процесс работает действительно хорошо, без задержек и зависаний, но иногда нет. Пожалуйста, обратите внимание, что этот подход предназначен для НЕ БЛОКИРОВКИ.
Я также попытался запустить цикл for внутри рабочего класса, чтобы скопировать все файлы без уведомления основного класса, но задержка велика, и графический интерфейс пользователя перестает отвечать при копировании больших файлов ( > 300 МБ). Например:
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));
}
}
}
Я надеюсь быть максимально понятным, это немного сложно объяснить. Я взял это как ссылку для работы с подходом рабочего класса.
ПРИМЕЧАНИЕ: я использую QT5.1.1 для программирования и Windows 10, и Arch Linux для развертывания приложения.
Любая помощь или совет приветствуется. Заранее спасибо и хорошего дня!
1 ответ
Ваш подход правильный, если немного многословный. "Задержка", вероятно, связана с тем, что ядро глупо относится к тому, как оно управляет кешем страниц и высвобождает части вашего приложения, чтобы освободить место для страниц из копируемых файлов. Единственным выходом из этого может быть использование механизма копирования файлов для конкретной платформы, который сообщает ядру, что вы будете последовательно читать и писать, и что вам больше не нужны данные после того, как вы сделали запись. Посмотрите этот ответ для деталей и некоторого кода Linux. В Windows можно надеяться, что CopyFileEx
делает правильные консультативные звонки, пример Qt здесь.