QT 界面阻塞相关事项
Q1 界面阻塞与资源初始化
问题描述:在启动一个界面时,需要执行一个耗时的初始化的操作,如果像下面这种写法,则会导致主线程被阻塞。
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
w.InitData();//一个耗时的操作
return a.exec();
}
方法一、延迟执行InitData
使用
QTimer::singleShot(10, &w, SLOT(InitData()));
或者
QMetaObject::invokeMethod(&w, "InitData", Qt::QueuedConnection);
方法二、在操作过程中使用qApp->processEvents();
//InitData
for (size_t i = 0; i <= 100; i++) {
qApp->processEvents();
for (size_t j = 0; j < 100000000; j++) { }//delay <==> QThread::msleep(123);
}
上述方法的弊端在于,不管是使用哪种方式,InitData这个操作都是在UI线程中完成的,qApp->processEvents();
可以让Qt在这个操作进行的过程中去处理事件,但是处理事件的效率受到这个delay的限制。如下图可以明显感受到进度条满了之后时间的刷新速度变快了。
考虑如下场景:
- 界面中设置了一个label,并通过一个timer以10ms一次的间隔设置其文本为当前时间。
- 界面中添加了一个进度条,并由InitData操作逐次修改进度值,其中InitData中的delay设置为1000ms。
经过测试,会出现如下情况:
-
InitData进行前,label每10ms刷新一次
-
InitData进行时,label刷新频率明显变慢
-
InitData结束后,label每10ms刷新一次
如何使得InitData的阻塞操作不影响UI线程,即label始终每10ms刷新一次?
方案三、使用线程来执行耗时操作
- 将InitData操作放到线程中执行,通过信号的方式与主线程进行交互,需要注意的,这种情况下不可以在线程中初始化MainWindow的线程独占成员如socket、串口设备等。
void MainWindow::InitData(){
for (size_t i = 0; i <= 100; i++) {
emit setValue(i);
QThread::msleep(1000);
}
}
int main(int argc, char* argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
std::thread t(std::bind(&MainWindow::InitData, &w));//建议使用QT推荐的QThread写法
return a.exec();
}
当MainWindow的一部分资源初始化耗时较大时,在线程中处理对UI线程的影响较小,这种情况下,使用线程来修改MainWindow的成员是避免不了的一件事,最为稳妥的办法就是,在线程中计算出变量的最终值,然后触发信号来通知MainWindow进行变量的更新,否则就需要使用锁机制。
引申场景
异步请求
- 考虑将请求过程放到线程空间,通过信号来控制发起请求和收到响应处理