即使我在单独的线程中运行,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()&lt;&lt;"function &lt;function&gt; called from: "&lt;&lt;QThread::currentThread()-&gt;objectName(); 【参考方案1】:

dbtrad-&gt;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-&gt;getHistory(channelId); 我尝试从 MainWindow 内部和线程内部打印出:qDebug() &lt;&lt; this-&gt;thread()-&gt;currentThreadId();,我得到相同的结果:0x7fff7300f180 使用您发布的代码,您仍在主 GUI 线程中。您需要显示新代码。【参考方案2】:

    QTcpSocket确实不需要在单独的线程中。

    只要所有的数据库访问都是从创建数据库的那个线程完成的,应该没问题

    现在是有趣的部分:我认为您在主线程中创建数据库...通过调用dbtrad-&gt;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-&gt;openDB 连接到来自主窗口的某个信号并以这种方式传递userID,或者像我一样使用元数据调用。

以上是关于即使我在单独的线程中运行,QT GUI 也会冻结的主要内容,如果未能解决你的问题,请参考以下文章

即使使用 QThread,QT 程序也会停止

响应式 Qt GUI,即使是线程

android - 延迟线程也会冻结其他活动

qt如何让控件在单独线程运行

QT QThread::isrunning 冻结程序在 Pi

主线程冻结所有其他线程,包括 java gui 线程