槽中的 QMutex

QMutex in slots

假设有如下QT代码(QT 5.3.1):

void SenderClass::runSignal()
{
    emit mySignal();
}

void ReceiverClass::ReceiverClass()
{
    ...
    connect (senderClassRef, SIGNAL(mySignal()), this, SLOT(mySlot()) );
}

void ReceiverClass::mySlot()
{
    //Long operation executions
    Sleep(1000);
    qDebug() << "1";
    Sleep(1000);
    qDebug() << "2";
    Sleep(1000);
    qDebug() << "3";
}

连续调用 runSignal() 控制台会显示如下内容:

1 2 1 3 2 3

2 类 生活在同一个线程中。 我必须在插槽中使用 QMutexLocker 吗?或者是否有另一种方法来获得有序输出,例如:

1 2 3 1 2 3

因此如果还有一个正在执行,则阻止调用 mySlot() 函数。

更新

下面是一个真实的代码片段。 发件人:

//Sender.h

#include <QtWidgets/QWidget>
#include <QtWidgets/QPushButton>
#include <QtWidgets/QTextBrowser>

class Receiver;

class Sender : public QWidget
{
    Q_OBJECT

public:
    Sender(QWidget *parent = 0);

    QPushButton *pushButton;
    QTextBrowser *textBrowser;
    Receiver* receiver;

signals:
    void buttonClickedSignal();

public slots:
    void ButtonClickedSlot();
};

//Sender.cpp

#include "Sender.h"
#include "Receiver.h"

#include <QObject>

Sender::Sender(QWidget *parent)
    : QWidget(parent)
{
    pushButton = new QPushButton(this);
    pushButton->setObjectName(QStringLiteral("pushButton"));
    pushButton->setGeometry(QRect(150, 30, 75, 23));
    pushButton->setText("Button");
    textBrowser = new QTextBrowser(this);
    textBrowser->setObjectName(QStringLiteral("textBrowser"));
    textBrowser->setGeometry(QRect(50, 90, 256, 192));

    receiver = new Receiver(this);

    QObject::connect(pushButton, SIGNAL(clicked()), this, SLOT(ButtonClickedSlot()));
}

void Sender::ButtonClickedSlot()
{
    emit buttonClickedSignal();
}

接收方:

//Receiver.h
#include "Sender.h"

class Receiver : QObject
{
    Q_OBJECT

public:
    Receiver(Sender *sender);

    Sender *m_sender;

public slots:
    void ReceiverSlot();
};

//Receiver.cpp
#include "Receiver.h"

#include <QObject>
#include <QThread>
#include <QApplication>

Receiver::Receiver(Sender *sender)
{
    m_sender = sender;

    QObject::connect(m_sender, SIGNAL(buttonClickedSignal()), this, SLOT(ReceiverSlot()), Qt::QueuedConnection);
}

void Receiver::ReceiverSlot()
{
    m_sender->textBrowser->append("1");
    QThread::msleep(100);
    qApp->processEvents();
    m_sender->textBrowser->append("2");
    QThread::msleep(100);
    qApp->processEvents();
    m_sender->textBrowser->append("3");
    QThread::msleep(100);
    qApp->processEvents();
    m_sender->textBrowser->append("4");
    QThread::msleep(100);
    qApp->processEvents();
}

即使设置了 QueuedConnection,快速单击按钮也会导致 QTextBrowser 中的数字不连续。

更新 2

我想要实现的是对 ReceiverSlot 的排队访问。用户可以随意点击按钮(在他想要的时候以任何速度)。 ReceiverSlot 不能错过任何事件。我需要某种事件排队,以便始终执行 ReceiverSlot 中的(长)操作,可能会延迟,但会执行。

要将我的评论变成正确的答案:

如果在你调用qApp->processEvents()的slot中有一个鼠标按下事件等待传递给按钮,它将被传递,并且clicked()信号将再次发出,因此您的插槽将被再次调用。

A QMutex 无济于事,因为不涉及线程。添加一个很可能会导致您的程序死锁。

使用 Qt::QueuedConnection 没有帮助,因为这会将一个事件发布到队列中,处理后会调用您的插槽。所以它充其量只是将它延迟到您下次调用 qApp->processEvents().

在您的示例上下文中,我的建议是摆脱排队的连接,而是在插槽工作时禁用按钮。这将防止再次发出 clicked() 信号,并向用户表明此时他们不应该尝试点击按钮。

如果出于某种原因您不想这样做,您可以向接收器添加一个标志 class 以防止递归调用。像这样的东西就可以了,虽然它不是很漂亮:

void Receiver::ReceiverSlot()
{
  if( m_inSlot )
    return;
  m_inSlot = true;
  // do work
  m_inSlot = false;
}

摆脱 QApplication::processEvents 并将冻结 GUI 的长时间操作移至新线程。例如,您可以使用移动到新线程的工作对象。有个good example of this in the docs.

您还应该阅读 this to learn more about threads and events in Qt. There is an exact same situation as yours explained in the forcing event dispatching 部分:

“通过其他路径”重新进入事件循环时要非常小心:它可能会导致不需要的递归!让我们回到 Button 示例。如果我们在 doWork() 槽中调用 QCoreApplication::processEvents(),并且用户再次点击按钮,doWork() 槽将再次被调用:

  • main(int, char **) QApplication::exec()
  • […]
  • QWidget::事件(QEvent *)
  • 按钮::mousePressEvent(QMouseEvent *)
  • 按钮::点击()
  • […]
  • Worker::doWork() // 首先,内部调用
  • QCoreApplication::processEvents() // 我们手动调度事件和...
  • […]
  • QWidget::event(QEvent * ) // 另一个鼠标点击被发送到 按钮…
  • 按钮::mousePressEvent(QMouseEvent *)
  • Button::clicked() // 再次发出 clicked()…
  • […]
  • Worker::doWork() // 该死!我们已经递归到我们的插槽中。

一般来说,您希望避免 QApplication::processEvents 并创建本地事件循环。如果你的代码中有这些,那么你应该重新设计它,这样你就不必使用它们了。

来自文档的 Worker 对象示例:

 class Worker : public QObject
 {
     Q_OBJECT
     QThread workerThread;

 public slots:
     void doWork(const QString &parameter) {
         // ...
         emit resultReady(result);
     }

 signals:
     void resultReady(const QString &result);
 };

 class Controller : public QObject
 {
     Q_OBJECT
     QThread workerThread;
 public:
     Controller() {
         Worker *worker = new Worker;
         worker->moveToThread(&workerThread);
         connect(workerThread, SIGNAL(finished()), worker, SLOT(deleteLater()));
         connect(this, SIGNAL(operate(QString)), worker, SLOT(doWork(QString)));
         connect(worker, SIGNAL(resultReady(QString)), this, SLOT(handleResults(QString)));
         workerThread.start();
     }
     ~Controller() {
         workerThread.quit();
         workerThread.wait();
     }
 public slots:
     void handleResults(const QString &);
 signals:
     void operate(const QString &);
 };