插槽中的QMutex

时间:2015-02-12 09:18:31

标签: c++ qt qt5 mutex

假设有以下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

这两个类存在于同一个线程中。 我是否必须在插槽中使用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();
}

快速单击该按钮会导致QTextBrowser中没有连续的数字,即使设置了QueuedConnection也是如此。

Output

更新2

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

2 个答案:

答案 0 :(得分:1)

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

您还应该阅读this以了解有关Qt中的线程和事件的更多信息。与您在forcing event dispatching部分中解释的情况完全相同:

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

  • main(int,char **)QApplication :: exec()
  • [...]
  • QWidget :: event(QEvent *)
  • Button :: mousePressEvent(QMouseEvent *)
  • 按钮::点击()
  • [...]
  • Worker :: doWork()//首先,内部调用
  • QCoreApplication :: processEvents()//我们手动调度事件和......
  • [...]
  • QWidget :: event(QEvent *)//发送另一个鼠标点击 按钮...
  • Button :: mousePressEvent(QMouseEvent *)
  • Button :: clicked()//再次发出clicked()......
  • [...]
  • Worker :: doWork()// DANG!我们已经进入了我们的位置。

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

来自文档的工作者对象检查:

 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 &);
 };

答案 1 :(得分:0)

将我的评论转化为正确答案:

如果,当您在插槽中拨打qApp->processEvents()时,鼠标按下事件等待传递到按钮,它将被传递,clicked()信号将再次发出,因此您的广告位将再次被调用。

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

使用Qt::QueuedConnection没有帮助,因为这会将一个事件发布到队列中,当处理该队列时,将导致您的插槽被调用。因此,它最多会延迟到下次您致电qApp->processEvents()

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

如果由于某种原因您不想这样做,您可以在接收器类中添加一个标志以防止递归调用。像这样的东西可以解决问题,虽然它不是很漂亮:

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