天津做网站贵吗,桓台县城乡建设局网站,网站导航结构,公关策划是做什么的一、阻塞型延时
阻塞的原理就是#xff1a;在延迟期间#xff0c;本线程的事件循环得不到处理
1.QThread类的sleep()
最简单的延时方法就是使用QThread类的sleep(n)、msleep(n)、usleep(n)#xff0c;这几个函数的不良后果就是#xff0c;GUI会在延时的时间段内失去响应…一、阻塞型延时
阻塞的原理就是在延迟期间本线程的事件循环得不到处理
1.QThread类的sleep()
最简单的延时方法就是使用QThread类的sleep(n)、msleep(n)、usleep(n)这几个函数的不良后果就是GUI会在延时的时间段内失去响应界面卡死所以这三个函数一般用在非GUI线程中。
QThread::msleep(50);//阻塞延时50ms2.使用计时器死等
void Delay_MSec_Suspend(unsigned int msec)
{ QTime _Time QTime::currentTime().addMSecs(msec);while( QTime::currentTime() _Time);
}二、非阻塞型延时
原理无非就是利用事件循环有两种原理
1.处理本线程的事件循环
在等待中不断强制刷新当前线程的事件循环这样可以把事件队列中被阻塞的时间都处理掉从而避免当前线程卡住(在主线程中就是避免程序卡死)
void Delay_MSec(unsigned int msec)
{QTime _Time QTime::currentTime().addMSecs(msec);while (QTime::currentTime() _Time){QCoreApplication::processEvents(QEventLoop::AllEvents, 100);}}QCoreApplication::processEvents(QEventLoop::AllEvents, 100);这条语句能够使程序在while等待期间去处理一次本线程的事件循环处理事件循环最多100ms必须返回本语句如果提前处理完毕则立即返回这条语句。这也就导致了该Delay_MSec函数的定时误差可能高达100ms。
2.使用子事件循环
创建子事件循环在子事件循环中父事件循环仍然是可以执行的
void Delay_MSec(unsigned int msec)
{QEventLoop loop;//定义一个新的事件循环QTimer::singleShot(msec, loop, SLOT(quit()));//创建单次定时器槽函数为事件循环的退出函数loop.exec();//事件循环开始执行程序会卡在这里直到定时时间到本循环被退出
}3.耗时代码的处理
假设有这样的应用情景点击某个button之后需要读入并处理一幅图像需要耗时20秒才能处理完。
在这20s内GUI会失去效应界面上的任何元素都无法被点击这种情况应该怎么办 方法有两种 1、用另一个线程去处理这个耗时任务 2、在耗时任务中不断地去处理本线程的事件循环以保证GUI的及时响应 这里重点说一下第2种参考 Qt线程与事件循环
for(i0; i 1000000; i)
{//QCoreApplication::processEvents(QEventLoop::AllEvents); //去处理本线程的事件循环避免本线程被堵塞QCoreApplication::processEvents(QEventLoop::AllEvents, 5);//如果不够频繁可以增加第二参数来缓解卡顿for(j0; j 1000000; j){//QCoreApplication::processEvents(QEventLoop::AllEvents);//处理事件循环不建议放在这里可能过于频繁doSomeThing();}
}一般来说processEvents()不宜被调用的过于频繁也不宜被调用的不够频繁。过于频繁的话一方面会使线程的响应更好但另一方面会导致原本就耗时的任务变得更加耗时不够频繁的话显然可能会使GUI线程的响应变差例如每500ms才被调用一次那么GUI的事件循环就只能500ms才被处理一次当然这个问题可以通过设定processEvents()的第二个形参略微得到缓解更好的做法是保证被调的周期200ms再小一些更好看程序需求这样不至于肉眼可见的卡顿。
副作用特别注意 1、在点击按钮之后这个20s的耗时任务开始执行尚未执行完毕时我们点击了GUI的关闭按钮那么GUI会立即消失但是这个耗时任务仍然会在后台执行直到执行完毕进程才会退出。解决办法重写关闭事件在关闭事件的函数中直接结束进程。
2、在点击按钮之后这个20s的耗时任务开始执行执行到第5秒时我们再次点击了这个按钮那么QT又会执行一个新的20s任务这个新任务完成后又会接着把第一个20s任务从上次被打断的第5秒继续执行。如果这个任务是可重入的后果仅仅是被执行了两遍如果任务不可重入那情况就彻底糟糕了。解决办法点击按钮后把这个按钮disable掉执行完再enable 在嵌入式开发中经常会遇到这样一种场景需要轮询获取某个设备的状态 例如某个场景需要不断获取 激光测距模块物理设备 的测距值但是激光测距模块此时不一定可以获取到正常的值就需要轮询不断的获取直到获取到一次正常的值之后打破轮询或是达到超时时间打破轮询
1.使用QTimer进行定时检查
#include QCoreApplication
#include QTimer
#include QDebugclass LaserDistanceSensor : public QObject
{Q_OBJECT
public:LaserDistanceSensor(QObject* parent nullptr): QObject(parent){m_pollTimer new QTimer(this);m_pollTimer-setInterval(200); //每200ms检查一次距离值是否有效connect(m_pollTimer, QTimer::timeout, this, LaserDistanceSensor::checkDistance);}//开始轮询void startPolling(){if (!m_pollTimer-isActive()){m_pollTimer-start();QTimer::singleShot(5000, this, LaserDistanceSensor::pollingTimeout); // 设置5秒超时,打破轮询}}// 模拟获取激光测距模块的值的函数double getDistanceValue(){return (rand() % 10 5) ? 1.0 : -1.0;}//检查是否已经获取到有效激光测距值void checkDistance(){double distance getDistanceValue();if (distance 0){qDebug() 有效的激光测距值: distance;m_pollTimer-stop(); // 停止轮询}}//轮询超时未获取到有效值void pollingTimeout(){if (m_pollTimer-isActive()){qDebug() 超时未获取有效激光测距值;m_pollTimer-stop();}}private:QTimer* m_pollTimer nullptr;
};外部调用 startPolling()函数就可以开始轮询了达到条件就会打破轮询请注意如果startPolling()函数被调用的线程与LaserDistanceSensor 对象创建的线程不一致QTimer就无法运行因为QTimer的运行依赖事件循环且只能在QTimer对象创建的线程中的事件循环中运行
非同一线程时Qt报错
QObject::startTimer: Timers cannot be started from another thread
QObject::startTimer: Timers can only be used with threads started with QThread这种情况时我们可以使用信号槽的方式解决此问题比如添加一个信号在槽函数中调用startPolling(),就可以让QTimer顺利运行信号可以在不具有事件循环的线程中发送只要接收对象所处的线程有事件循环即可发信号和QApplication::postEvent的作用机制类似都是朝receiver的事件队列中放入一个事件/信号等待receiver对象的事件循环来处理该事件/信号
class LaserDistanceSensor : public QObject
{Q_OBJECT
public:LaserDistanceSensor(QObject* parent nullptr): QObject(parent){m_pollTimer new QTimer(this);m_pollTimer-setInterval(200); //每200ms检查一次距离值是否有效connect(m_pollTimer, QTimer::timeout, this, LaserDistanceSensor::checkDistance);connect(this, LaserDistanceSensor::sglStartPolling, this, LaserDistanceSensor::startPolling);}void startPollingInOtherThread(){emit sglStartPolling();}//开始轮询void startPolling(){if (!m_pollTimer-isActive()){m_pollTimer-start();QTimer::singleShot(5000, this, LaserDistanceSensor::pollingTimeout); // 设置5秒超时,打破轮询}}// 模拟获取激光测距模块的值的函数double getDistanceValue(){return (rand() % 10 5) ? 1.0 : -1.0;}//检查是否已经获取到有效激光测距值void checkDistance(){double distance getDistanceValue();if (distance 0){qDebug() 有效的激光测距值: distance;m_pollTimer-stop(); // 停止轮询}}//轮询超时未获取到有效值void pollingTimeout(){if (m_pollTimer-isActive()){qDebug() 超时未获取有效激光测距值;m_pollTimer-stop();}}signals:void sglStartPolling();private:QTimer* m_pollTimer nullptr;
};外部调startPollingInOtherThread()函数即可。
2.使用While进行轮询
伪代码如下类似于非阻塞延时的第一种形式 bool validValueReceived false;// 设置超时时间为5秒const int timeoutSeconds 5;QTime startTime QTime::currentTime().addSecs(timeoutSeconds);while (!validValueReceived){// 检查是否超时QTime currentTime QTime::currentTime();if (startTime.secsTo(currentTime) timeoutSeconds){qDebug() 超时未获取有效激光测距值;break;}// 获取激光测距模块的值double distance getDistanceValue();if (distance 0){qDebug() 有效的激光测距值: distance;validValueReceived true;}// 处理事件确保应用程序响应QCoreApplication::processEvents();}参考 QEventLoop Qt线程基础