由Qt::BlockingQueuedConnection引起的关闭Qt主页面而后台仍有进程残留

BUG:由Qt::BlockingQueuedConnection引起的关闭Qt主页面而后台仍有进程残留

1、错误代码示例

首先我们看下下面的代码,可以思考一下代码的错误之处

/** BlockingQueueDeadLock.h **/
#pragma once

#include 
#include "ui_BlockingQueueDeadLock.h"
#include 

class BlockingQueueDeadLock : public QMainWindow
{
    Q_OBJECT

public:
    BlockingQueueDeadLock(QWidget *parent = nullptr);
    ~BlockingQueueDeadLock();

public slots:
	void RecvBlockingQueueSignal();
signals:
	void SendBlockingQueueSignal();

private:
	void startLoopTest();
	void stopLoopTest();
	void RunInThread();

private:
    Ui::BlockingQueueDeadLockClass ui;
	std::thread loopTest;
	bool m_StopFlag;
};

/** BlockingQueueDeadLock.cpp **/
#include "BlockingQueueDeadLock.h"
#include 

BlockingQueueDeadLock::BlockingQueueDeadLock(QWidget *parent)
    : QMainWindow(parent)
	, m_StopFlag(false)
{
    ui.setupUi(this);
	startLoopTest();
	connect(this, &BlockingQueueDeadLock::SendBlockingQueueSignal,
		this, &BlockingQueueDeadLock::RecvBlockingQueueSignal, Qt::BlockingQueuedConnection);
}

BlockingQueueDeadLock::~BlockingQueueDeadLock()
{
	stopLoopTest();
}

void BlockingQueueDeadLock::RunInThread()
{
	qDebug("%1", std::this_thread::get_id());
	while (!m_StopFlag) {
		std::this_thread::sleep_for(std::chrono::microseconds(10));
		qDebug("signal thread: %1", std::this_thread::get_id());
		emit SendBlockingQueueSignal();
	}
}

void BlockingQueueDeadLock::RecvBlockingQueueSignal()
{
	qDebug("slot thread: %1", std::this_thread::get_id());
}

void BlockingQueueDeadLock::startLoopTest()
{
	m_StopFlag = false;
	loopTest = std::thread(&BlockingQueueDeadLock::RunInThread, this);
}

void BlockingQueueDeadLock::stopLoopTest()
{
	m_StopFlag = true;
	if (loopTest.joinable()) {
		loopTest.join();
	}
}

上面短短几十行代码竟会导致当我关闭Qt主页面时,后台的进程并没有完全退出。
在这里插入图片描述

2、原因分析

先使用转储工具获取当前后台进程的堆栈信息;右击后台进程->创建转储文件
由Qt::BlockingQueuedConnection引起的关闭Qt主页面而后台仍有进程残留_第1张图片

这时会获得一个DMP文件,通过windbg分析该DMP文件,如下图所示
由Qt::BlockingQueuedConnection引起的关闭Qt主页面而后台仍有进程残留_第2张图片

我们可以清晰的看到后台进程一直在等待join函数的退出;阅读源码分析join最终调用的时_Thrd_join这个接口,该接口是阻塞的,需要等待线程的主函数运行结束后才会返回。

也就是说我们RunInThread线程主函数迟迟没有结束。

BlockingQueueDeadLock::~BlockingQueueDeadLock()
{
	stopLoopTest();
}

void BlockingQueueDeadLock::RunInThread()
{
	qDebug("%1", std::this_thread::get_id());
	while (!m_StopFlag) {
		std::this_thread::sleep_for(std::chrono::microseconds(10));
		qDebug("signal thread: %1", std::this_thread::get_id());
		emit SendBlockingQueueSignal();
	}
}
void BlockingQueueDeadLock::stopLoopTest()
{
	m_StopFlag = true;
	if (loopTest.joinable()) {
		loopTest.join();
	}
}

线程的主函数就是一个while循环,在我们BlockingQueueDeadLock析构的时候会将标识符m_StopFlag置为true;按道理讲该while循环应该很快的结束并返回。

但是!但是!但是!我们是否忽略了emit这个信号发射的是如何connect的呢?

connect(this, &BlockingQueueDeadLock::SendBlockingQueueSignal,
		this, &BlockingQueueDeadLock::RecvBlockingQueueSignal, Qt::BlockingQueuedConnection);

非常非常奇怪的是为什么connect最后一个参数是Qt::BlockingQueuedConnection呢?我看到这个代码也会很奇怪,可能之前的开发者认为发送信号和接受信号的slot不在同一个线程吧!!

3、解决方案

奇怪的地方必有妖,没错就是Qt::BlockingQueuedConnection这里有问题。

先看Qt官方文档的解释:
由Qt::BlockingQueuedConnection引起的关闭Qt主页面而后台仍有进程残留_第3张图片

非常明确的指出了发射的信号与接收的槽不能在同一个线程里,否者会导致应用死锁。

想要了解Qt BlockingQueuedConnection源码的同学可以看下面的文章:

14.QueuedConnection和BlockingQueuedConnection连接方式源码分析_Master Cui的博客-CSDN博客
在这里插入图片描述

明确的指出了使用BlockingQueuedConnection同一个线程时死锁的原因。

因此,我们只需要将Qt::BlockingQueuedConnection最后的参数去除,使用默认参数即可。

这就是我发现的Qt主界面关闭,而后台进程未释放的问题;还是由于后台进程中的某个线程没有释放,而该线程没有结束又是由于Qt::BlockingQueuedConnection死锁导致的。

4、总结

当前Qt主界面关闭,而后台进程未释放的原因有很多。这里只是展示了其中一个原因:后台进程中有线程未及时释放导致的,也为遇到同样问题的你提供一个思路。

你可能感兴趣的:(QT/QML,日常问题,qt,开发语言)