众所周知的,Qt的一个核心的消息机制是信号与槽。当用户点击了某个按钮执行某一个非常庞大要执行很久才成完成的操作时,如果我们没有用线程,那么所有的消息循环全部都在app.exec()中完成。
app.exec()就是一个很大的循环函数,它的功能就是遍历所有控制,看有没有消息要处理。如果有,则执行。只有等执行完成才能进行下一个操作。如果一个操作耗了大量的时间,那么别的事件都没法做了,包括界面刷新、鼠标事件等。
backgroundworker.cpp:
注意看构造函数哦!moveToThread(&thread)就是将this对象的信号与槽机制同主线程转交给thread线程去处理。然后就thread.start()启动线程。
在释构函数中,一定要先停线程quit()并等待wait()其正常退出其消息循环,否则退出就会出报线程未停止的异常。
(2)在次线程中,要不处理GUI相关的内容,包括弹出消息对话框。因为主线程主要负责GUI图像处理操作。如果线程也要插一手,那么可能就会引起冲突了。这样很不安全。
(3)在次线程中,不要使用eventFilter(),这也是不安全的。
executableobject.cpp文件:
前面的BackgroundWorker可以精简成:
app.exec()就是一个很大的循环函数,它的功能就是遍历所有控制,看有没有消息要处理。如果有,则执行。只有等执行完成才能进行下一个操作。如果一个操作耗了大量的时间,那么别的事件都没法做了,包括界面刷新、鼠标事件等。
为了解决这个问题,我们必须得用线程来做。将大工作量的活交给次线程去作,主线程(app.exe())不会阻塞继续作界面上的工作。
举个例子:BackgroundWorker是次线程类,用于做一个复杂的运行(我们假设它很复杂)
backgroundworker.h
#ifndef BACKGROUNDWORKER_H
#define BACKGROUNDWORKER_H
#include <QThread>
class BackgroundWorker : public QObject
{
Q_OBJECT
public:
BackgroundWorker();
~BackgroundWorker();
signals:
void updateResultSignal(double area);
public slots:
void calculateSlot(double radio);
private:
QThread thread;
};
#endif // BACKGROUNDWORKER_H
backgroundworker.cpp:
#include "backgroundworker.h"
#include <qmath.h>
BackgroundWorker::BackgroundWorker()
{
moveToThread(&thread);
thread.start();
// TODO other initialize
}
BackgroundWorker::~BackgroundWorker()
{
thread.quit();
thread.wait(); // wait the thread quit normally
}
void BackgroundWorker::calculateSlot(double radio)
{
double area = 0.0;
area = qPow(radio, 2) * M_PI;
sleep(2); // OH! assume that it is a big work cost 2 seconds to finish it.
emit updateResultSignal(area);
}
注意看构造函数哦!moveToThread(&thread)就是将this对象的信号与槽机制同主线程转交给thread线程去处理。然后就thread.start()启动线程。
在释构函数中,一定要先停线程quit()并等待wait()其正常退出其消息循环,否则退出就会出报线程未停止的异常。
BackgroundWorker::calculateSlot()为计算圆面积的函数。我们假设它的计算量很大,需要花上2秒钟才能完成。主线程在接受到周户的操作时,发送信号给BackgroundWorker,发送信号后主线程就继续处理其它事件去了。由于主线程与BackgroundWorker不是同一个线程,那么消息则是通过消息列队传到BackgroundWorker的,当处理这个BackgroundWorker消息循环的thread线程接收到消息队列里的消息后,会调用calculateSlot()槽进行计算。在完成计算后,再通过发送updateResultSignal()信号将结果返回给主线程,同样是通过消息队列的方式。
其它需要说明的:
(1)如果不同线程间信号发送中的参数有自定义的数据类型,那么就必须先注册到Qt内部的类型管理器中后才能在connect()中使用。
qRegisterMetaType<MyType>("MyType");
(2)在次线程中,要不处理GUI相关的内容,包括弹出消息对话框。因为主线程主要负责GUI图像处理操作。如果线程也要插一手,那么可能就会引起冲突了。这样很不安全。
(3)在次线程中,不要使用eventFilter(),这也是不安全的。
(4)不一定非要在类中包含一个QThread,也可以在外面定义一个QThread对象,并把多个对象moveToThread()到同一个线程去处理。
(5)默认情况下,一个对象是由哪个线程实例化的,那么它的消息机制就是由哪个线程来处理。
写一个ExecutableObject类,让所有继承该类的派生类都有独立的消息处理线程。
executableobject.h文件:
#ifndef EXECUTABLEOBJECT_H
#define EXECUTABLEOBJECT_H
#include <QObject>
#include <QThread>
class ExecutableObject : public QObject
{
Q_OBJECT
public:
explicit ExecutableObject(QObject *parent = 0);
~ExecutableObject();
private:
QThread thread;
};
#endif // EXECUTABLEOBJECT_H
executableobject.cpp文件:
#include "executableobject.h"
ExecutableObject::ExecutableObject(QObject *parent) :
QObject(parent)
{
moveToThread(&thread);
thread.start();
}
ExecutableObject::~ExecutableObject()
{
thread.quit();
thread.wait();
}
前面的BackgroundWorker可以精简成:
#ifndef BACKGROUNDWORKER_H
#define BACKGROUNDWORKER_H
#include <QThread>
#include "executableobject.h"
class BackgroundWorker : public ExecutableObject
{
Q_OBJECT
public:
BackgroundWorker() {}
signals:
void updateResultSignal(double area);
public slots:
void calculateSlot(double radio);
};
#endif // BACKGROUNDWORKER_H
?
1
2
3
4
5
6
7
8
9
10
11
#include "backgroundworker.h"
#include <qmath.h>
void BackgroundWorker::calculateSlot(double radio)
{
double area = 0.0;
area = qPow(radio, 2) * M_PI;
sleep(2); // OH! assume that it is a big work cost 2 seconds to finish it.
emit updateResultSignal(area);
}