txt manejo lista leer guardar español datos creator como cargar archivos archivo c++ multithreading qt io

c++ - manejo - ¿Cómo puedo cargar de manera asincrónica datos de archivos grandes en Qt?



manejo de archivos en qt (3)

Antes que nada, no tienes ningún multihilo en tu aplicación. Su clase FileReader es una subclase de QThread , pero no significa que todos los métodos de FileReader se ejecutarán en otro hilo. De hecho, todas sus operaciones se realizan en el hilo principal (GUI).

FileReader debería ser una subclase QObject y no QThread . Luego, crea un objeto QThread básico y lo mueve a su trabajador (lector) usando QObject::moveToThread . Puedes leer sobre esta técnica aquí .

Asegúrese de haber registrado FileReader::State type con qRegisterMetaType . Esto es necesario para que las conexiones de ranura de señal Qt funcionen a través de diferentes hilos.

Un ejemplo:

HexViewer::HexViewer(QWidget *parent) : QMainWindow(parent), _ui(new Ui::HexViewer), _fileReader(new FileReader()) { qRegisterMetaType<FileReader::State>("FileReader::State"); QThread *readerThread = new QThread(this); readerThread->setObjectName("ReaderThread"); connect(readerThread, SIGNAL(finished()), _fileReader, SLOT(deleteLater())); _fileReader->moveToThread(readerThread); readerThread->start(); _ui->setupUi(this); ... } void HexViewer::on_quitButton_clicked() { _fileReader->thread()->quit(); _fileReader->thread()->wait(); qApp->quit(); }

Además, no es necesario asignar datos en el montón aquí:

while(!inFile.atEnd()) { QByteArray *qa = new QByteArray(inFile.read(DATA_SIZE)); qDebug() << "emitting dataRead()"; emit dataRead(qa); }

QByteArray usa el intercambio implícito . Significa que sus contenidos no se copian una y otra vez cuando pasa un objeto QByteArray través de funciones en un modo de solo lectura.

Cambie el código anterior a esto y olvídese de la administración manual de la memoria:

while(!inFile.atEnd()) { QByteArray qa = inFile.read(DATA_SIZE); qDebug() << "emitting dataRead()"; emit dataRead(qa); }

Pero de todos modos, el principal problema no es con multihilo. El problema es que el funcionamiento de QTextEdit::insertPlainText no es barato, especialmente cuando tienes una gran cantidad de datos. FileReader lee datos de archivos con bastante rapidez y luego inunda su widget con nuevas porciones de datos para mostrar.

Debe tenerse en cuenta que tiene una implementación muy ineficaz de HexViewer::loadData . QTextEdit datos de texto char por char, lo que hace que QTextEdit dibujar constantemente sus contenidos y congele la GUI.

Primero debe preparar la secuencia hexagonal resultante (tenga en cuenta que el parámetro de datos ya no es un puntero):

void HexViewer::loadData(QByteArray data) { QString tmp = data.toHex(); QString hexString; hexString.reserve(tmp.size() * 1.5); const int hexLen = 2; for (int i = 0; i < tmp.size(); i += hexLen) { hexString.append(tmp.mid(i, hexLen) + " "); } _ui->hexTextView->insertPlainText(hexString); }

De todos modos, el cuello de botella de su aplicación no es la lectura de archivos sino la actualización de QTextEdit . Cargar datos por fragmentos y luego agregarlos al widget utilizando QTextEdit::insertPlainText no acelerará nada. Para archivos de menos de 1Mb es más rápido leer todo el archivo a la vez y luego establecer el texto resultante en el widget en un solo paso.

Supongo que no puede visualizar fácilmente textos enormes de más de varios megabytes utilizando widgets Qt predeterminados. Esta tarea requiere una aproximación no trivial que, en general, no tiene nada que ver con la carga de datos asíncrona o de subprocesamiento múltiple. Se trata de crear un widget complicado que no intente mostrar sus enormes contenidos a la vez.

Estoy usando Qt 5.2.1 para implementar un programa que lee los datos de un archivo (podría ser unos pocos bytes a unos pocos GB) y visualiza esos datos de una manera que depende de cada byte. Mi ejemplo aquí es un visor hexadecimal.

Un objeto hace la lectura y emite una señal dataRead() cuando se lee un nuevo bloque de datos. La señal lleva un puntero a un QByteArray así:

filereader.cpp

void FileReader::startReading() { /* Object state code here... */ { QFile inFile(fileName); if (!inFile.open(QIODevice::ReadOnly)) { changeState(STARTED, State(ERROR, QString())); return; } while(!inFile.atEnd()) { QByteArray *qa = new QByteArray(inFile.read(DATA_SIZE)); qDebug() << "emitting dataRead()"; emit dataRead(qa); } } /* Emit EOF signal */ }

El visor tiene su ranura loadData conectada a esta señal, y esta es la función que muestra los datos:

hexviewer.cpp

void HexViewer::loadData(QByteArray *data) { QString hexString = data->toHex(); for (int i = 0; i < hexString.length(); i+=2) { _ui->hexTextView->insertPlainText(hexString.at(i)); _ui->hexTextView->insertPlainText(hexString.at(i+1)); _ui->hexTextView->insertPlainText(" "); } delete data; }

El primer problema es que si esto se ejecuta tal como está, el hilo de la GUI dejará de responder por completo. Todas las señales de dataRead() se emitirán antes de volver a dibujar la GUI.

(Se puede ejecutar el código completo , y cuando use un archivo de más de 1 KB, verá este comportamiento).

Atendiendo a la respuesta a mi publicación en el foro , el archivo local sin bloqueo IO en Qt5 y la respuesta a otra pregunta sobre el desbordamiento de pila ¿ Cómo hacer un archivo asíncrono io en qt? , la respuesta es: use hilos. Pero ninguna de estas respuestas entra en detalles sobre cómo barajar los datos en sí, ni cómo evitar errores y riesgos comunes.

Si los datos fueran pequeños (del orden de cien bytes), simplemente los emitiría con la señal. Pero en el caso de que el archivo tenga un tamaño GB ( editar ) o si el archivo está en un sistema de archivos basado en red, por ejemplo. NFS, Samba share, no quiero que la IU se bloquee solo porque lea los bloques de archivos.

El segundo problema es que la mecánica de usar new en el emisor y delete en el receptor parece un poco ingenua: estoy usando efectivamente todo el montón como una cola de hilos cruzados.

Pregunta 1: ¿Qt tiene una forma mejor / idiomática de mover datos a través de subprocesos mientras se limita el consumo de memoria? ¿Tiene una fila de hilos seguros u otras estructuras que pueden simplificar todo esto?

Pregunta 2: ¿Tengo que implementar el enhebrado, etc.? No soy un gran fan de reinventar las ruedas, especialmente con respecto a la gestión de la memoria y el enhebrado. ¿Hay construcciones de nivel superior que ya pueden hacer esto, como las que existen para el transporte de red?


  1. si está planeando editar archivos de QTextEdit olvidó de QTextEdit . Este ui->hexTextView->insertPlainText simplemente consumirá toda la memoria antes de leer 1/10 del archivo. OMI debe usar QTableView para presentar y editar datos. Para hacerlo, debes heredar QAbstractTableModel . En una fila, debes presentar 16 bytes. En las primeras 16 columnas en forma hexadecimal y en la columna siguiente en forma ASCII. Esto no debería ser complejo. Acabo de leer temerosamente la documentación de QAbstractTableModel . Los datos de almacenamiento en caché serán más importantes aquí. Si tengo tiempo, daré un ejemplo de código.

  2. Olvidé sobre el uso de múltiples hilos. Este es un mal caso para usar tal cosa y muy probablemente creará muchos problemas relacionados con la sincronización.

Ok, tuve algo de tiempo aquí es el código que está funcionando (he probado que funciona sin problemas):

#include <QObject> #include <QFile> #include <QQueue> class LargeFileCache : public QObject { Q_OBJECT public: explicit LargeFileCache(QObject *parent = 0); char geByte(qint64 pos); qint64 FileSize() const; signals: public slots: void SetFileName(const QString& filename); private: static const int kPageSize; struct Page { qint64 offset; QByteArray data; }; private: int maxPageCount; qint64 fileSize; QFile file; QQueue<Page> pages; }; #include <QAbstractTableModel> class LargeFileCache; class LageFileDataModel : public QAbstractTableModel { Q_OBJECT public: explicit LageFileDataModel(QObject *parent); // QAbstractTableModel int rowCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const; QVariant data(const QModelIndex &index, int role) const; signals: public slots: void setFileName(const QString &fileName); private: LargeFileCache *cachedData; }; #include "lagefiledatamodel.h" #include "largefilecache.h" static const int kBytesPerRow = 16; LageFileDataModel::LageFileDataModel(QObject *parent) : QAbstractTableModel(parent) { cachedData = new LargeFileCache(this); } int LageFileDataModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return (cachedData->FileSize() + kBytesPerRow - 1)/kBytesPerRow; } int LageFileDataModel::columnCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return kBytesPerRow; } QVariant LageFileDataModel::data(const QModelIndex &index, int role) const { if (index.parent().isValid()) return QVariant(); if (index.isValid()) { if (role == Qt::DisplayRole) { qint64 pos = index.row()*kBytesPerRow + index.column(); if (pos>=cachedData->FileSize()) return QString(); return QString::number((unsigned char)cachedData->geByte(pos), 0x10); } } return QVariant(); } void LageFileDataModel::setFileName(const QString &fileName) { beginResetModel(); cachedData->SetFileName(fileName); endResetModel(); } #include "largefilecache.h" const int LargeFileCache::kPageSize = 1024*4; LargeFileCache::LargeFileCache(QObject *parent) : QObject(parent) , maxPageCount(1024) { } char LargeFileCache::geByte(qint64 pos) { // largefilecache if (pos>=fileSize) return 0; for (int i=0, n=pages.size(); i<n; ++i) { int k = pos - pages.at(i).offset; if (k>=0 && k< pages.at(i).data.size()) { pages.enqueue(pages.takeAt(i)); return pages.back().data.at(k); } } Page newPage; newPage.offset = (pos/kPageSize)*kPageSize; file.seek(newPage.offset); newPage.data = file.read(kPageSize); pages.push_front(newPage); while (pages.count()>maxPageCount) pages.dequeue(); return newPage.data.at(pos - newPage.offset); } qint64 LargeFileCache::FileSize() const { return fileSize; } void LargeFileCache::SetFileName(const QString &filename) { file.close(); file.setFileName(filename); file.open(QFile::ReadOnly); fileSize = file.size(); }

Es más corto de lo que esperaba y necesita algunas mejoras, pero debería ser una buena base.


Este parece ser el caso en el que le gustaría tener un productor de consumo con semáforos. Hay un ejemplo muy específico que puede ayudarlo a implementarlo correctamente. Necesitas un hilo más para que esto funcione aparte de tu hilo principal.

La configuración debería ser:

  • El subproceso A ejecuta tu lector de archivos como productor
  • Su hilo de GUI ejecuta su widget Hexviewer que consume sus datos en eventos específicos. Antes de emitir QSemaphore::acquire() una comprobación con QSemaphore :: available () ` para evitar el bloqueo de la GUI.
  • Filereader y Hexviewer tienen acceso a una tercera clase, por ejemplo, DataClass, donde los datos se leen y se recuperan del consumidor. Esto también debería tener los semáforos definidos.
  • No hay necesidad de emitir una señal con los datos o notificar.

Eso cubre prácticamente mover su lectura de datos del lector de archivos a su widget, pero no cubre cómo pintar realmente esta información. Para lograr esto, puede consumir los datos dentro de un evento anulando el evento de pintura de Hexviewer, y leyendo lo que se ha puesto en la cola. Un enfoque más elaborado sería escribir un filtro de evento .

Además de esto, es posible que desee tener un número máximo de bytes de lectura después de lo cual Hexviewer se indica explícitamente para consumir los datos.

Tenga en cuenta que esta solución es completamente asíncrona, segura y ordenada, ya que ninguno de sus datos se envía a Hexviewer, pero el Hexviewer solo consume eso cuando necesita mostrarse en la pantalla.