我们要实现的是读取大文件qtgui.index
的内容加入文本框中。
很容易想到的方法:1
2
3
4
5
6
7
8
9QFile* file = new QFile("E:\qtgui.index");
file->open(QIODevice::ReadOnly);
QTextStream *stream = new QTextStream(file);
while(!stream->atEnd())
{
QString line = stream->readLine();
ui->textEdit->append(line);
}
结果运行后发现程序失去响应。因为读取大文件要很长时间,事件循环一直等待函数返回,这样导致阻塞事件循环。结果,GUI线程所有的绘制和交互都被阻塞在事件队列中,无法执行重绘等事件,整个程序就失去响应了。
解决阻塞一般有两种方法:
手动强制事件循环
在任务中不断调用QCoreApplication::processEvents()手动强制事件循环,它会在处理完队列中所有事件后返回。但是如果两次函数调用的间隔时间不够短,用户仍能明显感觉到程序卡顿。所以在while循环最后加一行QApplication::processEvents();
即可。
多线程处理。
Qt提供了三种方式:QThread、QRunnable / QThreadPool、QtConcurrent。其中最常用的是 QThread。
对于本例,使用QThread又有三种方法:信号与槽实现线程间通信、元对象系统实现线程间通信、分离线程与任务。前两种也是跨线程调用函数的方法。
使用QThread
信号与槽实现线程间通信
这是线程间通信比较常用的方法。代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34class ReadThread : public QThread
{
Q_OBJECT
public:
ReadThread(QObject* obj);
signals:
void toLine(QString line);
protected:
void run() Q_DECL_OVERRIDE;
private:
QFile* file;
QObject* m_obj;
};
ReadThread::ReadThread(QObject* obj):
m_obj(obj)
{
file = new QFile("E:\qtgui.index");
}
void ReadThread::run()
{
file->open(QIODevice::ReadOnly);
QTextStream *stream = new QTextStream(file);
while(1)
{
while(!stream->atEnd())
{
QString line = stream->readLine();
emit toLine(line);
QThread::msleep(15);
}
}
}
需要把读取任务放到run()
里,构造函数要传入GUI类的指针
在GUI线程的信号与槽机制这样:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
ReadThread* thread = new ReadThread(this);
thread->start();
connect(thread,SIGNAL(toLine(QString)),this,SLOT(appendText(QString)) );
connect(thread,SIGNAL(finished()),this,SLOT(FinishThread()) );
}
void MainWindow::appendText(QString lineTemp)
{
ui->textEdit->append(lineTemp);
}
其中appendText
是MainWindow的槽函数,Q_ARG的两个形参分别为槽函数的形参类型和实参。
在使用invokeMethod
方法后,使用了QThread的静态函数msleep,因为读取的文件太大,每读取一行就要更新GUI,太耗资源,会导致GUI忙不过来,读一行后稍微休息一下,否则也会阻塞GUI。
1 | QMetaObject::invokeMethod(m_obj,"appendText",Qt::AutoConnection, |
1 | ReadThread* thread = new ReadThread(this); |
1 | class MyThread : public QThread { |
1 | class MyObj : public QObject |
1 | void MyObj::doWork() |
1 | t = new QThread(); //QThread |
代码中的默认connect类型是Qt::AutoConnection,如果在一个线程就是Qt::DirectConnection,不在一个线程就是Qt::QueuedConnection;
如果是Qt::DirectConnection
,相当于直接调用槽函数,但是当信号发出的线程和槽的对象不在同一个线程的时候,槽函数是在发出的信号中执行的。所以appendText在子线程。
如果是Qt::QueuedConnection
,线程安全,内部通过postEvent实现的。不是实时调用的,槽函数永远在槽函数对象所在的线程中执行。所以appendText在GUI线程
QueuedConnection的线程情况:
DirectConnection 的线程情况:
AutoConnection 的线程情况:
同步调用:发出信号后,当前线程等待槽函数执行完毕后才继续执行。
异步调用:发出信号后,立即执行剩下逻辑,不关心槽函数什么时候执行。
总结: