【问题标题】:Qt GUI becomes unresponsive emitting signals too fastQt GUI 变得反应迟钝,发射信号太快
【发布时间】:2014-12-02 18:00:49
【问题描述】:

我有一个小型聊天客户端,它将所有历史记录存储在 sqlite 数据库中。当用户在我的应用程序中单击history 选项卡时,我的应用程序会获取所有相关历史记录并将其显示在QWebView 中。我从下面的后台线程dbThread 获取,然后发送信号以相应地更新QWebView。

这工作正常,直到数据库增长。当数据库变大时,应用程序开始几乎崩溃。 GUI 会在几秒钟内无响应,直到所有内容都加载完毕(4-6 秒),具体取决于database 的大小。

我已经尝试在信号上添加Qt::QueuedConnection,并且如上所述,我正在处理来自background thread 的所有database 查询。

我猜我发出信号的速度太快了。任何想法如何解决这个问题?

信号

connect(dbtrad, SIGNAL(addAllHistoryMessage(QString, QString, QString, QString, QString)), this, SLOT(addAllHistoryMessage(QString, QString, QString, QString, QString)), Qt::QueuedConnection);
connect(dbtrad, SIGNAL(addAllHistoryMessageInner(QString, QString, QString, QString, QString)), this, SLOT(addAllHistoryMessageInner(QString, QString, QString, QString, QString)), Qt::QueuedConnection);

从 sqlite 数据库中获取历史记录的代码:

// Loads all local history
void dbThread::loadAllHistory(QString agentID, QString agentName) {
    bool ret = false;
    bool retInner = false;
    QString retVal = "";

    QDateTime dateTime = dateTime.currentDateTime();
    QString dateTimeForTodayCheck = dateTime.toString("yyyy-MM-dd");

    if (db.isOpen()) {
        QSqlQuery query(db);
        QSqlQuery queryInner(db);

        ret = query.exec(QString("SELECT channelID, sender, time, message from chatHistory WHERE sender != 'ServerMessage' AND channelID NOT LIKE '%Agent%' GROUP BY channelID order by time DESC;"));

        if (ret) {

            while (query.next()) {    
                QString channelID = query.value(0).toString();
                QString sender = query.value(1).toString();
                QString time = query.value(2).toString();
                QString msg = query.value(3).toString();


                QString timeStr;
                QString fmt = "yyyy-MM-dd hh:mm:ss";
                QDateTime dt = QDateTime::fromString(time, fmt);
                QDateTime dtCompare = QDateTime::fromString(time, fmt);

                if(dateTimeForTodayCheck == dtCompare.toString("yyyy-MM-dd")) { // If today
                    timeStr = "Today " + dt.toString("hh:mm");
                } else {
                    timeStr = dt.toString("dd MMM yyyy");
                }

                if(sender == agentID) {
                    sender = agentName;
                }
                // Grab all the tags
                QString tempTagsForChannelID = getHistoryTagsString(channelID);

                emit addAllHistoryMessage(channelID, sender, timeStr, msg, tempTagsForChannelID);

                // Load sub-history
                retInner = queryInner.exec(QString("SELECT * from chatHistory WHERE sender != 'ServerMessage' AND channelID = '%1' and message != '%2' order by time DESC;").arg(channelID).arg(msg));

                if (retInner) {
                    while (queryInner.next()) {
                        QString channelIDInner = queryInner.value(0).toString();
                        QString senderInner = queryInner.value(1).toString();
                        QString timeInner = queryInner.value(4).toString();
                        QString msgInner = queryInner.value(2).toString();

                        QString timeStr2;
                        QString fmt = "yyyy-MM-dd hh:mm:ss";
                        QDateTime dt = QDateTime::fromString(timeInner, fmt);
                        QDateTime dtCompare = QDateTime::fromString(timeInner, fmt);

                        if(dateTimeForTodayCheck == dtCompare.toString("yyyy-MM-dd")) { // If today
                            timeStr2 = "Today " + dt.toString("hh:mm");
                        } else {
                            timeStr2 = dt.toString("dd MMM yyyy");
                        }

                        if(senderInner == agentID) {
                            senderInner = agentName;
                        }

                        emit addAllHistoryMessageInner(channelIDInner, senderInner, timeStr2, msgInner, tempTagsForChannelID);
                    }
                }
            }
        }
    }
}

我要更新的代码:

void MainWindow::addAllHistoryMessageInner(QString channelIDInner, QString senderInner, QString timeStr2, QString msgInner, QString tempTagsForChannelID) {
    ui->webViewHistory->page()->mainFrame()->evaluateJavaScript("$('#history tbody').append('<tr id=\"" + channelIDInner+ "\" class=\"hiddenRow\"><td>" + senderInner + "</td><td align=\"center\">" + timeStr2 + "</td><td align=\"center\" style=\"word-wrap:break-word;\">" + msgInner.remove(QRegExp("<[^>]*>")) + "</td><td align=\"center\">" + tempTagsForChannelID + "</td></tr>');undefined");
}

void MainWindow::addAllHistoryMessage(QString channelID, QString sender, QString timeStr, QString msg, QString tempTagsForChannelID) {
    ui->webViewHistory->page()->mainFrame()->evaluateJavaScript("$('#history tbody').append('<tr id=\"" + channelID + "\"><td>" + sender + "</td><td align=\"center\">" + timeStr + "</td><td align=\"center\" style=\"word-wrap:break-word;\">" + msg.remove(QRegExp("<[^>]*>")) + "</td><td align=\"center\" style=\"word-wrap:break-word;\">" + tempTagsForChannelID + "</td></tr>');undefined");
}

编辑:dbThread 的实现

thread = new QThread(this);
dbtrad = new dbThread();
dbtrad->moveToThread(thread);

编辑 2:我就是这样称呼loadAllHistory

我创建一个信号:

connect(this, SIGNAL(loadAllHistoryS(QString, QString)), dbtrad, SLOT(loadAllHistory(QString, QString)));

然后这样称呼它:

emit loadAllHistoryS(agentID, agentName);

【问题讨论】:

  • 您的数据库连接是否在dbThread 中创建? dbThread真的是在一个单独的线程中运行的,它是如何实现的?
  • 我正在打开dbThread 内的数据库连接,请参阅上面线程的实现:)
  • 你怎么称呼loadAllHistory?
  • 更新我的称呼
  • 您是否在thread.start() 的线程上启动了事件循环?会不会结果是在同一个线程而不是后台线程上有效地同步执行(导致主线程由于数据库I/O而阻塞)?

标签: c++ multithreading qt


【解决方案1】:

问题是,innerQuery 中的每一行都会中断主线程。这破坏了在单独的线程中加载数据的好处。可能线程边界上的信号/槽通信开销甚至高于从数据库加载单行的成本。

我建议在QList 实例while 循环中收集行。完成后,通过一次信号调用将完整结果推送到主线程:

首先声明一个简单的类来存储历史项目:

class HistoryItem {
    public:
        QString channelID;
        /* additonal fields omitted for brevity */
        /* also, private fields with getters and setters would be better */
}

然后,在while 循环之前创建一个此类对象的列表:

QList<HistoryItem*> innerResult;
while (queryInner.next()) {
      /* snip */
      HistoryItem* item = new HistoryItem();
      item.channelId = channelIDInner;
      /* more lines ommited */
      innerResult.append(historyItem);
}
emit historyLoaded(innerResult);

显然,您还需要在工作人员类中定义匹配的信号:

Q_SIGNAL void historyLoaded(QList<HistoryItem*> result);    

同样如 cmets 中所述,您必须使用 QThread::start() 启动后台线程。

【讨论】:

  • 我如何将结果存储在QList 中,然后以某种方式在主线程中循环它?
  • 在线程设置代码@ratchetfreak 正确之后,我已经更正了那部分
  • 另外,鉴于它是一种 POD 类型,您可以按值将它们存储在 QList 中,(您需要先调用 qRegisterMetaType&lt;QList&lt;HistoryItem&gt;&gt;("QList&lt;HistoryItem&gt;"); 并输入 Q_DECLARE_METATYPE(HistoryItem),然后才能执行信号槽操作)
  • 无法触发信号historyLoaded。像这样添加它:connect(dbtrad, SIGNAL(historyLoaded(QList)), this, SLOT(historyLoaded(QList)));
  • 参数类型不完整:QList&lt;HistoryItem*&gt;.
【解决方案2】:

您可能会受益于持续加载:

您只加载将在视图中显示的元素,并且仅在它们滚动到视图中时才请求下一组(或者通常在后台以低得多的速度)。

这可以通过Adding an object to the window object 和适当的信号来完成,并让 js 在结尾可见时触发它。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-17
    • 2015-02-20
    • 2017-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-10-12
    相关资源
    最近更新 更多