det*_*tly 7 c++ io qt multithreading
我正在使用Qt 5.2.1来实现一个程序,该程序从文件中读取数据(可能是几个字节到几GB),并以依赖于每个字节的方式显示数据.我的例子是一个十六进制查看器.
一个对象执行读取,并dataRead()在读取新数据块时发出信号.信号带有一个指向QByteArray类似的指针,所以:
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 */
}
Run Code Online (Sandbox Code Playgroud)
查看器的loadData插槽连接到此信号,这是显示数据的功能:
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;
}
Run Code Online (Sandbox Code Playgroud)
第一个问题是如果这只是按原样运行,GUI线程将完全没有响应.dataRead()在重新绘制GUI之前,将发出所有信号.
(可以运行完整代码,当您使用大于1kB的文件时,您将看到此行为.)
通过对我的论坛的回复发布Qt5中的非阻塞本地文件IO以及另一个Stack Overflow问题的答案如何在qt中执行异步文件io?,答案是:使用线程.但这些答案都没有详细说明如何改变数据本身,也不知道如何避免常见的错误和陷阱.
如果数据很小(大约一百字节)我只是用信号发出它.但是如果文件的大小是GB(编辑),或者文件是基于网络的文件系统,例如.NFS,Samba共享,我不希望UI只是因为读取文件块而锁定.
第二个问题是new在发射器和delete接收器中使用的机制看起来有点幼稚:我实际上是将整个堆用作跨线程队列.
问题1:在限制内存消耗的同时,Qt是否有更好/惯用的方式跨线程移动数据?它是否有一个线程安全队列或其他可以简化整个事情的结构?
问题2:我自己是否必须实施线程等?我不是重塑轮子的忠实粉丝,特别是在内存管理和线程方面.是否有更高级别的构造可以做到这一点,就像网络传输一样?
首先,您的应用程序中根本没有任何多线程.您的FileReader类是其子类QThread,但并不意味着所有FileReader方法都将在另一个线程中执行.实际上,所有操作都在主(GUI)线程中执行.
FileReader应该是一个QObject而不是一个QThread子类.然后,您创建一个基本QThread对象,并使用您的工作者(读者)移动它QObject::moveToThread.你可以在这里阅读这个技术.
确保您已FileReader::State使用注册类型qRegisterMetaType.这对于Qt信号槽连接在不同线程上工作是必要的.
一个例子:
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();
}
Run Code Online (Sandbox Code Playgroud)
此外,没有必要在堆上分配数据:
while(!inFile.atEnd())
{
QByteArray *qa = new QByteArray(inFile.read(DATA_SIZE));
qDebug() << "emitting dataRead()";
emit dataRead(qa);
}
Run Code Online (Sandbox Code Playgroud)
QByteArray使用隐式共享.这意味着当您QByteArray以只读模式跨函数传递对象时,不会反复复制其内容.
将上面的代码更改为此,忘记手动内存管理:
while(!inFile.atEnd())
{
QByteArray qa = inFile.read(DATA_SIZE);
qDebug() << "emitting dataRead()";
emit dataRead(qa);
}
Run Code Online (Sandbox Code Playgroud)
但无论如何,主要问题不在于多线程.问题是QTextEdit::insertPlainText操作并不便宜,尤其是当您拥有大量数据时.FileReader快速读取文件数据,然后使用要显示的新数据部分填充窗口小部件.
必须注意的是,你的实现非常无效HexViewer::loadData.您可以通过char插入文本数据char,这会QTextEdit不断重绘其内容并冻结GUI.
您应该首先准备生成的十六进制字符串(请注意,数据参数不再是指针):
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);
}
Run Code Online (Sandbox Code Playgroud)
无论如何,您的应用程序的瓶颈不是文件读取,而是QTextEdit更新.按块加载数据然后将其附加到窗口小部件QTextEdit::insertPlainText将不会加快任何速度.对于小于1Mb的文件,一次读取整个文件然后在一个步骤中将结果文本设置为窗口小部件会更快.
我想你不能使用默认的Qt小部件轻松地显示大于几兆字节的巨大文本.此任务需要一些非平凡的approch,通常与多线程或异步数据加载无关.这一切都是关于创建一些棘手的小部件,它不会试图立即显示其巨大的内容.