目录
1. 函数说明
processEvents函数如下:
[static] void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents)
[static] void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int ms)
可以看到有两个重载函数,且都是QCoreApplication静态成员函数,第二个函数多了超时时间参数,基本同第一函数功能类似。其作用是:
- 如果正在处理事件的时间超过了指定的时间限制 ms,processEvents 函数仍然会等待事件处理完成,然后返回。
- 如果事件处理完成前的时间限制 ms 达到了,那么 processEvents 函数将立即返回,即使事件队列中仍有待处理的事件。
- 为调用线程即调用本函数所在的线程处理所有阻塞事件,具体处理哪些类型的阻塞事件由本函数的参数flags决定。当在程序的某个耗时操作偶尔调用一下本函数,可以解决界面冻结、卡死、显示不出来的问题(见后面的例子)。
本函数的QEventLoop::ProcessEventsFlags参数是枚举类型,各枚举值说明如下:
枚举值 | 具体值 | 作用及含义 |
AllEvents | 0x00 | 所有事件,但DeferredDelete被特殊处理,具体参见Qt Assist中的QObject::deleteLater()的说明 |
ExcludeUserInputEvents | 0x01 | 不处理鼠标、键盘事件。注意:鼠标、键盘事件不会被丢弃,它只是不处理而已,等下次调用processEvents函数且参数不是ExcludeUserInputEvents时才处理鼠标、键盘事件 |
ExcludeSocketNotifiers | 0x02 | 不处理套接字通知事件。注意:套接字通知事件不会被丢弃,它只是不处理而已,等下次调用processEvents函数且参数不是ExcludeSocketNotifiers时才处理鼠标、键盘事件 |
WaitForMoreEvents | 0x04 | 如果没有阻塞事件,则一直等待 |
可以通过位或操作(|),将上述参数联合起来用,如:
QEventLoop::ProcessEventsFlags(QEventLoop::AllEvents | QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
上述代码的意思是:除了不处理键盘、鼠标、套接字通知事件外,其它事件都处理。
2. 需求应用场景举例
2.1. 场景1
如下代码:
CQueryDataProgressDlg exportProgressDlg(this); // 进度提示框窗体
exportProgressDlg.show();
exportProgressDlg.setRange(1,100); // 进度条范围为[1, 100]
exportProgressDlg.setTip(QstringLiteral("正在导出数据,请稍候..."));
for (auto pageIndex = 0, exportProgress = 1; pageIndex < 100; ++pageIndex, ++exportProgress) // 数据共100页
{
//QCoreApplication::processEvents();
//QCoreApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
// 按页导出数据库中的数据
exportDataToLocal(pageIndex); // 此句代码非常耗时
// 每导出1页数据,进度条就向前推进一步
exportProgressDlg.setPos(exportProgress);
}
读者不必纠结上述代码的完整实现,可以将上述代码理解为伪代码,CQueryDataProgressDlg 可以理解为一个进度提示框窗体,上面的代码是按页索引(pageIndex)导出数据库中的数据到本地。其中exportDataToLocal函数负责按页索引查询数据库并将查询结果保存到本地,其非常耗时,每导出一页数据,CQueryDataProgressDlg窗体表示的进度条就前进一格,这种边执行边告知用户执行进度的需求,在现实中很常见,然而,如果注释掉或不加第7行代码,则进度条窗体压根不会显示出来,因为导出数据很耗时导致Qt的事件循环被阻塞,进度提示框窗体事件得不到执行而不显示、进度条不会更新。加入第7行代码后,进度条窗体显示如下且进度条能按预定设想的那样向前走动,且按住鼠标左键拖动窗体时,窗体能被拖动;可以尝试在该窗体加入一个编辑框,此时编辑框能接受键盘输入。
如果将第7行换成第8行,即processEvents函数的参数改为QEventLoop::ExcludeUserInputEvents,则按住鼠标左键拖动窗体时,窗体不能被拖动,即鼠标事件被屏蔽了,可以尝试在该窗体加入一个编辑框,此时编辑框不接受任何键盘输入。
2.2. 场景2
线程中有死循环,导致没有机会处理事件,执行槽函数。
// Work类的槽函数:
// 先触发这个槽函数,里面有while循环,会卡住线程
void Worker::doWork()
{
qDebug() << __FUNCTION__ << " Thread ID: " << QThread::currentThreadId() << "\n";
// 循环10次,每次1s
int i = 1;
while (i < 10)
{
i++;
Sleep(1000);
qDebug() << "second : " << i << " s\n";
//QCoreApplication::processEvents(); //
}
}
// 再触发这个槽函数,会因为线程在做while循环,没有机会得到执行
void Worker::doWork2()
{
qDebug() << __FUNCTION__ << " Thread ID: " << QThread::currentThreadId();
}
----------------------------------------------------------------------------------------------------------
// Main函数中的代码
auto *worker = new Worker;
_workerThread = new QThread(this);
worker->moveToThread(_workerThread); // worker的槽函数都在_workerThread线程中执行
connect(this, SIGNAL(operate()), worker, SLOT(doWork(int)));
connect(this, SIGNAL(operate2()), worker, SLOT(doWork2(int)));
// 启动线程
_workerThread->start();
emit operate(); // 先让线程中的while循环跑起来
emit operate2(); // 再触发doWork2槽函数,但是因为子线程中doWork在while循环,
// 此事件没有机会被处理,直至while循环结束才得到doWork2执行的机会。
结果1:
注掉QCoreApplication::processEvents(); 阻塞情况:
Worker::doWork Thread ID: 0x4260
second : 2 s
second : 3 s
second : 4 s
second : 5 s
second : 6 s
second : 7 s
second : 8 s
second : 9 s
Worker::doWork2 Thread ID: 0x4260
结果2:
放开注释 QCoreApplication::processEvents(); 解决阻塞问题后:
Worker::doWork Thread ID: 0x4260
second : 2 s
Worker::doWork2 Thread ID: 0x4260 // 执行processEvents时,如果有事件被阻塞了,立刻执行其槽函数
second : 3 s
second : 4 s
second : 5 s
second : 6 s
second : 7 s
second : 8 s
second : 9 s