关于在macOS上运行Qt窗口程序遇到的坑
单纯的我最近在macOS上写Qt GUI,写得还算爽,虽然C++的手动清内存还没习惯(Qt的半自动内存管理也救不了啊)。
发现问题
直到昨天试着写一个TcpServer时,才发现了不对劲,在我写TcpSocket定时发送数据(用QTimer)时,发现QTimer开始的时候是正常执行代码,过个几十秒后,程序就会停顿2、3秒(甚至10秒)
思考问题
我以为的原因一
一开始我以为是QTcpSocket::write的问题,因为QTcpSocket继承自IODevice,所以write后不一定会直接发送,而是先放入QTcpSocket的缓存空间里。所以解决方法是在write代码执行顺序后面追加代码
// 手动flush缓存
socket->flush();
然而,还是没有解决。
我以为的原因二
因为我设计TcpServer的时候为了能让TcpServer同时处理多个用户访问,所以使用多线程技术。通过重写方法:
QTcpServer::incomingConnection(qintptr handle)
来实现多线程处理socket,具体代码如下:
void TcpServer::incomingConnection(qintptr handle) {
socket = new TcpSocket(handle);
auto *thread = new QThread;
socket->moveToThread(thread);
connect(socket, &TcpSocket::disconnected, socket, &TcpSocket::onDisconnected);
connect(socket, &TcpSocket::readyRead, socket, &TcpSocket::read);
connect(socket, &TcpSocket::stateChanged, socket, &TcpSocket::onStatusChanged);
connect(socket, &TcpSocket::tcpConnect, socket, &TcpSocket::onConnected);
connect(socket, &TcpSocket::tcpDisconnect, this, &TcpServer2::onDisconnected);
thread->start();
// res是QHash,用将socket和对应的thread联系,方便最后disconnect时删除thread
res.insert(socket, thread);
emit socket->tcpConnect();
emit newConnection();
}
然后聪明的我猜想:会不会是线程等级不够高,导致线程不调度时间少?
所以我特地为修改QThread::start方法:
// 最高优先级了
thread->start(QThread::Priority::TimeCriticalPriority);
当然,你和我一样聪明,你就会知道这一点用都没有。
我以为的原因三
是否是TCP协议的原因呢?比如因为定时间隔时间太短(实际上我定时间隔为1s)导致基于拥塞窗口的拥塞控制发挥作用,直接导致传输变慢?
不可能啊,变慢能变慢到7秒8秒才发送到?况且我发送的数据字节数很少啊。
然后我想,可能跟QTcpSocket没关系,于是我写了一个简单的定时器程序。值得一提的是,写了两个,一个是将QTimer放在主线程,另一个是将QTimer放在子线程。
写了才发现,都有一样有问题的!不可能啊?就一个定时器程序怎么也有问题?
于是聪明的我开始思考到底哪里有问题。不是QTcpSocket的问题,不是QThread的问题,还有什么问题?
于是搜遍了某度、Gxxgle、Bxxg。
然后并没有找到我要的答案。
百思不得其解中,我发现一个问题:当我将窗口程序放置在桌面顶层时(我写的是Widget项目,虽然没有写任何窗口),程序是正常执行定时器操作的。于是我使用Qt Console Application创建方式重新写了一个计时器程序(使用QCoreApplication启动),值得惊喜的是,不管将程序放置在屏幕顶层还隐藏,该程序能正常运行!
我以为的原因四
经过上述实验,我感觉自己离知道真相进了一步,于是我大胆猜测:Qt程序不在屏幕顶层时,UI线程会随机地进入休眠。(因为命令行程序不采用UI线程为主线程,所以不会有影响)
于是我带着这个假设将定时器程序(使用QApplication启动的)放在Windows7上跑,结果让我震惊:居然是成功运行的。
我的最终回猜想
我发现了,macOS是内鬼吧。
于是我认为macOS操作系统下,非屏幕顶层的窗口程序主线程(UI线程)会被macOS不定时地休眠几秒钟。
ps.有大佬能解释一下这个现象吗?
2019年8月2日更新:
找到解决方法,就是在widget里加个定时器负责刷新widget(注意,要使用repaint方法,update方法被优化过),这样可以防止macOS不定时休眠进程。