Qt之QThread深入理解Word文档格式.docx
- 文档编号:20921231
- 上传时间:2023-01-26
- 格式:DOCX
- 页数:17
- 大小:29.14KB
Qt之QThread深入理解Word文档格式.docx
《Qt之QThread深入理解Word文档格式.docx》由会员分享,可在线阅读,更多相关《Qt之QThread深入理解Word文档格式.docx(17页珍藏版)》请在冰豆网上搜索。
<
"
WorkerThread:
<
QThread:
:
currentThreadId();
}
protected:
virtualvoidrun()Q_DECL_OVERRIDE{
WorkerRunThread:
intnValue=0;
while(nValue<
100)
//休眠50毫秒
msleep(50);
++nValue;
//准备更新
emitresultReady(nValue);
signals:
voidresultReady(intvalue);
};
∙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
构建一个主界面-包含按钮、进度条,当点击“开始”按钮时,启动线程,更新进度条。
classMainWindow:
publicCustomWindow
explicitMainWindow(QWidget*parent=0)
CustomWindow(parent)
MainThread:
//创建开始按钮、进度条
QPushButton*pStartButton=newQPushButton(this);
m_pProgressBar=newQProgressBar(this);
//设置文本、进度条取值范围
pStartButton->
setText(QString:
fromLocal8Bit("
开始"
));
m_pProgressBar->
setFixedHeight(25);
setRange(0,100);
setValue(0);
QVBoxLayout*pLayout=newQVBoxLayout();
pLayout->
addWidget(pStartButton,0,Qt:
AlignHCenter);
addWidget(m_pProgressBar);
setSpacing(50);
setContentsMargins(10,10,10,10);
setLayout(pLayout);
//连接信号槽
connect(pStartButton,SIGNAL(clicked(bool)),this,SLOT(startThread()));
~MainWindow(){}
privateslots:
//更新进度
voidhandleResults(intvalue)
HandleThread:
setValue(value);
//开启线程
voidstartThread()
WorkerThread*workerThread=newWorkerThread(this);
connect(workerThread,SIGNAL(resultReady(int)),this,SLOT(handleResults(int)));
//线程结束后,自动销毁
connect(workerThread,SIGNAL(finished()),workerThread,SLOT(deleteLater()));
workerThread->
start();
private:
QProgressBar*m_pProgressBar;
WorkerThreadm_workerThread;
∙31
∙32
∙33
∙34
∙35
∙36
∙37
∙38
∙39
∙40
∙41
∙42
∙43
∙44
∙45
∙46
∙47
∙48
∙49
∙50
∙51
∙52
∙53
∙54
∙55
显然,UI界面、Worker构造函数、槽函数处于同一线程(主线程),而run()函数处于另一线程(次线程)。
0x34fc
0x4038
0x34fc
由于信号与槽连接类型默认为“Qt:
AutoConnection”,在这里相当于“Qt:
QueuedConnection”。
也就是说,槽函数在接收者的线程(主线程)中执行。
注意:
信号与槽的连接类型,请参考:
Qt之Threads和QObjects中“跨线程的信号和槽”部分。
线程休眠
上述示例中,通过在run()函数中调用msleep(50),线程会每隔50毫秒让当前的进度值加1,然后发射一个resultReady()信号,其余时间什么都不做。
在这段空闲时间,线程不占用任何的系统资源。
当下一次CPU时钟来临时,它会继续执行。
QThread提供了静态的、平台独立的休眠函数:
sleep()、msleep()、usleep(),允许秒,毫秒和微秒来区分,函数接受整型数值作为参数,以表明线程挂起执行的时间。
当休眠时间结束,线程就会获得CPU时钟,将继续执行它的指令。
想象一下,日常用的电脑,如果我们需要离开一段时间,可以将它设置为休眠状态,为了节约用电,同时响应国家政策-走绿色、环保之道。
可以尝试注释掉休眠部分的代码,这时,由于没有任何耗时操作,会造成频繁地更新UI。
所以,为了保证界面的流畅性,同时确保进度的更新在人眼可接受的范围内,我们应在必要的时候加上适当时间的休眠。
在主线程中更新UI
当连接方式更改为“Qt:
DirectConnection”时:
connect(workerThread,SIGNAL(resultReady(int)),this,SLOT(handleResults(int)),Qt:
DirectConnection);
再次点击“开始”按钮,会很失望,因为它会出现一个异常,描述如下:
ASSERTfailureinQCoreApplication:
sendEvent:
“Cannotsendeventstoobjectsownedbyadifferentthread.Currentthreade346e8.ReceivercustomWidget’(oftype‘MainWindow’)wascreatedinthread4186a0”,filekernel\qcoreapplication.cpp,line553
显然,UI界面、Worker构造函数处于同一线程(主线程),而run()函数、槽函数处于同一线程(次线程)。
0x2c6c
0x4704
0x4704
之所以会出现这种情况是因为Qt做了限制(其它大多数GUI编程也一样),不允许在其它线程(非主线程)中访问UI控件,这么做主要是怕在多线程环境下对界面控件进行操作会出现不可预知的情况。
所以,不难理解,由于在槽函数(次线程)中更新了UI,所以,会引起以上错误。
避免多次connect
当多次点击“开始”按钮的时候,就会多次connect(),从而启动多个线程,同时更新进度条。
为了避免这个问题,我们修改如下:
//...
connect(&
m_workerThread,SIGNAL(resultReady(int)),this,SLOT(handleResults(int)));
if(!
m_workerThread.isRunning())
m_workerThread.start();
将connect添加在构造函数中,保证了信号槽的正常连接。
在线程start()之前,可以使用isFinished()和isRunning()来查询线程的状态,判断线程是否正在运行,以确保线程的正常启动。
优雅地结束线程
如果一个线程运行完成,就会结束。
可很多情况并非这么简单,由于某种特殊原因,当线程还未执行完时,我们就想中止它。
不恰当的中止往往会引起一些未知错误。
比如:
当关闭主界面的时候,很有可能次线程正在运行,这时,就会出现如下提示:
QThread:
Destroyedwhilethreadisstillrunning
这是因为次线程还在运行,就结束了UI主线程,导致事件循环结束。
这个问题在使用线程的过程中经常遇到,尤其是耗时操作。
在此问题上,常见的两种人:
∙直接忽略此问题。
∙强制中止-terminate()。
大多数情况下,当程序退出时,次线程也许会正常退出。
这时,虽然抱着侥幸心理,但隐患依然存在,也许在极少数情况下,就会出现Crash。
正如前面提到过terminate(),比较危险,不鼓励使用。
线程可以在代码执行的任何点被终止。
线程可能在更新数据时被终止,从而没有机会来清理自己,解锁等等。
总之,只有在绝对必要时使用此函数。
举一个简单的例子:
当你的哥们正处于长时间的酣睡状态时,你想要叫醒他,但是采取的措施却是泼一盆凉水,想象一下后果?
这凉爽-O(∩_∩)O哈哈~。
所以,我们应该采取合理的措施来优雅地结束线程,一般思路:
1.发起线程退出操作,调用quit()或exit()。
2.等待线程完全停止,删除创建在堆上的对象。
3.适当的使用wait()(用于等待线程的退出)和合理的算法。
下面介绍两种方式:
∙QMutex互斥锁+bool成员变量。
这种方式是Qt4.x中比较常用的,主要是利用“QMutex互斥锁+bool成员变量”的方式来保证共享数据的安全性(可以完全参照下面的requestInterruption()源码写法)。
QMutexLocker>
QThread(parent),
m_bStopped(false)
~WorkerThread()
stop();
quit();
wait();
voidstop()
WorkerStopThread:
QMutexLockerlocker(&
m_mutex);
m_bStopped=true;
//检测是否停止
if(m_bStopped)
break;
//locker超出范围并释放互斥锁
boolm_bStopped;
QMutexm_mutex;
∙56
∙57
∙58
为什么要加锁?
很简单,是为了共享数据段操作的互斥。
何时需要加锁?
在形成资源竞争的时候,也就是说,多个线程有可能访问同一共享资源的时候。
当主线程调用stop()更新m_bStopped的时候,run()函数也极有可能正在访问它(这时,他们处于不同的线程),所以存在资源竞争,因此需要加锁,保证共享数据的安全性。
∙Qt5以后:
requestInterruption()+isInterruptionRequested()
这两个接口是Qt5.x引入的,使用很方便:
~WorkerThread(){
//请求终止
requestInterruption();
//是否请求终止
while(!
isInterruptionRequested())
//耗时操作
在耗时操作中使用isInterruptionRequested()来判断是否请求终止线程,如果没有,则一直运行;
当希望终止线程的时候,调用requestInterruption()即可。
正如侯捷所言:
「源码面前,了无秘密」。
如果还心存疑虑,我们不妨来看看requestInterruption()、isInterruptionRequested()的源码:
voidQThread:
requestInterruption()
Q_D(QThread);
d->
mutex);
running||d->
finished||d->
isInFinish)
return;
if(this==QCoreApplicationPrivate:
theMainThread){
qWarning("
requestInterruptionhasnoeffectonthemainthread"
);
d->
interruptionRequested=true;
}
boolQThread:
isInterruptionRequested()const
Q_D(constQThread);
returnfalse;
returnd->
interruptionRequested;
^_^,内部实现居然也用了互斥锁QMutex,这样我们就可以放心地使用了。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Qt QThread 深入 理解
![提示](https://static.bdocx.com/images/bang_tan.gif)