即使我在单独的线程中运行,QT GUI 也会冻结
Posted
技术标签:
【中文标题】即使我在单独的线程中运行,QT GUI 也会冻结【英文标题】:QT GUI freezes even though Im running in separate thread 【发布时间】:2014-05-22 07:45:26 【问题描述】:我有一个小型聊天应用程序,我使用 SQLite 数据库存储所有对话。我注意到该应用程序随机冻结,然后我必须最小化和最大化它以使其再次工作。我认为问题可能是导致 gui 冻结的 SQLite 选择/插入。我决定尝试将所有 SQLite 方法移动到一个单独的线程中。
这样做后,应用程序仍然冻结。
一些可能值得了解的事情:
我在MainWindow
中直接使用QTcpSocket
,但在单独的线程中运行QTcpSocket
似乎没有用?
我已将 SQLite 方法分离到一个新线程中(参见下面的实现)
我使用 3 个WebViews
来显示我的聊天消息,整个应用程序 GUI 都是使用这些 WebViews
构建的
我下面的代码真的在单独的线程中运行吗? GUI仍然冻结。
我的头文件:
class dbThread : public QObject
Q_OBJECT
public:
dbThread(QObject* parent);
public slots:
bool openDB(QString agentID);
signals:
void clearPreviousHistory();
private:
QSqlDatabase db;
QHash<QString, QString> countries;
;
我的 cpp 文件:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
QThread* thread = new QThread(this);
dbtrad = new dbThread(this);
dbtrad->moveToThread(thread);
dbtrad->openDB(userID);
connect(dbtrad, SIGNAL(clearPreviousHistory()), this, SLOT(clearHistoryV()));
thread->start();
dbThread::dbThread(QObject * parent): QObject(parent)
bool dbThread::openDB(QString agentID)
qDebug() << "OPEN DB FROM THREAD ";
// Find QSLite driver
db = QSqlDatabase::addDatabase("QSQLITE");
// ......
这就是我从MainWindow
调用dbThread
方法的方式:
dbtrad->getHistory(channelId);
编辑
新代码:
// Start database thread
QThread* thread = new QThread(this);
dbtrad = new dbThread(this);
dbtrad->moveToThread(thread);
connect(this, SIGNAL(requestOpenDB(QString)), dbtrad, SLOT(openDB(QString)));
thread->start();
emit requestOpenDB(userID);
【问题讨论】:
显示调用 dbthread 函数的代码。你也从主线程调用它们吗?你可能误解了moveToThread
的作用。
查看我的编辑,我已经发布了我如何从我的主线程调用 db 方法
QThread::currentThreadId () Warning: The handle returned by this function is used for internal purposes and should not be used in any application code. Warning: On Windows, the returned value is a pseudo-handle for the current thread
真正的问题是。它还结冰吗?
@Alosyius 为新线程命名并调用qDebug()<<"function <function> called from: "<<QThread::currentThread()->objectName();
【参考方案1】:
dbtrad->openDB(userID);
将像任何普通函数一样执行(为什么要这样?),在 GUI 线程中。
moveToThread
允许您在单独的线程中执行使用信号调用的槽。
如果你想在线程中执行openDB
,你可以使用
connect (thread, SIGNAL(started()), dbtrad, SLOT(openDBWithUIDAlreadySet()))
或
connect (this, SIGNAL(requestOpenDB(int)), dbtrad, SLOT(openDB(int)))
您需要使用现有的或附加的信号。 Qthread::start()
发出信号 started()
。你也可以定义
MainWindow
signals:
void requestOpenDB(int);
void queryHistory(int channelid);
并使用手动发出信号
emit requestOpenDB(userID); //for openDB
emit queryHistory(channelId); // for getHistory
dbThread
对象的响应也需要使用连接到插槽的信号给出。像通知一样。
【讨论】:
啊,我明白了!我会用connects更新我所有的数据库调用,看看是否有效 我将如何使用连接进行此调用?dbtrad->getHistory(channelId);
我尝试从 MainWindow 内部和线程内部打印出:qDebug() << this->thread()->currentThreadId();
,我得到相同的结果:0x7fff7300f180
使用您发布的代码,您仍在主 GUI 线程中。您需要显示新代码。【参考方案2】:
QTcpSocket
确实不需要在单独的线程中。
只要所有的数据库访问都是从创建数据库的那个线程完成的,应该没问题
现在是有趣的部分:我认为您在主线程中创建数据库...通过调用dbtrad->openDB(userId)
【讨论】:
【参考方案3】:是的,所以 qt moveToThread()
不会做你期望它做的事情。您从主线程调用的函数将仅在您的主线程中执行。该数据库访问导致 GUI 冻结。
moveToThread
仅在单独的线程中移动“事件处理”。这意味着使用Qt::QueuedConnection
连接的dbThread
的任何插槽都将在新线程中执行。
以下方式将仅在您的主 ui 线程中执行 getHistory()
方法。您需要在主线程中创建一个信号并使getHistory()
成为dbThread
类的插槽。然后连接两者。
【讨论】:
【参考方案4】:阅读文档和日志是必不可少的!!! 在日志中你有一个警告,如果对象有一个父对象,你就不能移动到线程。 还有documentation clearly says that:
更改此对象及其子对象的线程亲和性。 该 如果对象有父对象,则不能移动它。事件处理将 在 targetThread 中继续。
修复它的正确方法:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
thread = new QThread(this);
dbtrad = new dbThread(); // NO PARENT
dbtrad->moveToThread(thread);
// run object method in thread assigned to this object:
QMetaObject::invokeMethod(dbtrad, "openDB", Qt::QueuedConnection, Q_ARG(QString, userID));
connect(dbtrad, SIGNAL(clearPreviousHistory()), this, SLOT(clearHistoryV()));
thread->start();
MainWindow::~MainWindow()
dbtrad->deleteLater();
thread->quit();
thread->wait(5000); // wait max 5 seconds to terminate thread
【讨论】:
如何解决这个问题?不知道我知道要改变什么才能让它工作 不设置父级,见编辑。您还应该将dbtrad->openDB
连接到来自主窗口的某个信号并以这种方式传递userID
,或者像我一样使用元数据调用。以上是关于即使我在单独的线程中运行,QT GUI 也会冻结的主要内容,如果未能解决你的问题,请参考以下文章