插槽只用一个线程互相中断?

Slots interrupting each other with just one Thread?

我有一个使用由 mousemovement 和 mousewheel 调用的插槽的小示例。

现在我遇到的问题是,当我同时缩放和移动时,首先调用 onZoom-slot,然后在完成之前调用 onMouseMoved-slot。这导致第一个插槽锁定互斥锁(在我原来的程序中被另一个线程使用),第二个插槽等待它。

如何防止插槽相互中断(为什么它们首先要这样做,因为它们在同一个线程中?)。

我阅读了一些有关使用 Qt::QueuedConnection 的内容,但这会导致访问冲突异常。

main.cpp

#include "ppi.h"
#include <QtGui/QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    PPI w;
    w.show();
    return a.exec();
}

ppi.h

#ifndef PPI_H
#define PPI_H

#include <QtGui/QMainWindow>
#include <QGraphicsView>
#include <QDebug>
#include <QWheelEvent>
#include <QgraphicsEllipseItem>
#include <QMouseEvent>
#include <QMutex>
#include <QThread>
#include <QGraphicsSceneMouseEvent>

//#include "ui_ppi.h"

class PPIView : public QGraphicsView
{
        Q_OBJECT

    public:
        PPIView(QWidget * parent = 0)
            : QGraphicsView(parent)
        {};
        ~PPIView(){};

    private slots:
        void wheelEvent(QWheelEvent *event)
        {emit zoom(event);};

    signals:
        void zoom(QWheelEvent *event);

};

class PPIScene : public QGraphicsScene
{
        Q_OBJECT

    public:
        PPIScene(QObject *parent)
            : QGraphicsScene(parent)
        {};

        ~PPIScene(){};

    private:
        void mouseMoveEvent(QGraphicsSceneMouseEvent *event)
        {emit mouseMoved(event);};

    signals:
        void mouseMoved(QGraphicsSceneMouseEvent *event);
};

class PPI : public QMainWindow
{
        Q_OBJECT

    public:
        PPI(QWidget *parent = 0, Qt::WFlags flags = 0)
        : QMainWindow(parent, flags)
        {
            //ui.setupUi(this);
            //ppiScene is inherited from QGraphicsScene, overriding mouseMoveEvent so it emits mouseMoved();
            ppiScene = new PPIScene(this);
            gVPPI = new PPIView(this);
            gVPPI->setMinimumSize(1024,1024);
            gVPPI->show();

            test = new QGraphicsEllipseItem(-10, -10, 20, 20);
            ppiScene->addItem(test);

            gVPPI->adjustSize();

            connect(ppiScene, SIGNAL(mouseMoved(QGraphicsSceneMouseEvent*)), this, SLOT(onMouseMoved(QGraphicsSceneMouseEvent*)));
            connect(gVPPI, SIGNAL(zoom(QWheelEvent*)), this, SLOT(onZoom(QWheelEvent*)));

            //ui.gVPPI is inherited from QGraphicsView, overriding wheelEvent, so it emits zoom()
            gVPPI->setScene(ppiScene);
            gVPPI->setMouseTracking(true);
        };

        ~PPI(){};

        QMutex mutex;

    private:
        //Ui::ppiClass ui;
        PPIScene* ppiScene;
        PPIView *gVPPI;

        QGraphicsEllipseItem *test;

    protected slots:
        void onZoom(QWheelEvent *event)
        {
            qDebug() << "Zoom lock" << QThread::currentThreadId();
            mutex.lock();
            qDebug() << "Zoom locked";

            if(event->delta() > 0)
                gVPPI->scale(1.01, 1.01);
            else
                gVPPI->scale(1/1.01, 1/1.01);

            qDebug() << "Zoom unlock";
            mutex.unlock();
            qDebug() << "Zoom unlocked";
        };

        void onMouseMoved(QGraphicsSceneMouseEvent *event)
        {
            qDebug() << "Move lock" << QThread::currentThreadId();
            mutex.lock();
            qDebug() << "move locked";

            test->setPos(test->pos()+event->scenePos()-event->lastScenePos());

            qDebug() << "Move unlock";
            mutex.unlock();
            qDebug() << "Move unlocked";
    };
};

#endif // PPI_H

输出qDebug()

Move lock 0x1514 
move locked 
Move unlock 
Move unlocked 
Move lock 0x1514 
move locked 
Move unlock 
Move unlocked 
Zoom lock 0x1514 
Zoom locked 
Move lock 0x1514 

您可以使用QObject::blockSignals来阻止特定对象的信号, 但我认为您对代码的工作方式有错误的认识。

有一个GUI线程,如果signals+slot在同一个GUI线程, 就像你的情况一样,然后他们按顺序调用。如果 PPI::onZoom 将在另一个线程上下文中调用,那么你就会遇到问题,因为在非 GUI 线程中使用 ui.gVPPI->scale 之类的东西是不允许的,并且可能导致断言崩溃,或者只是崩溃,或者随机有效,UB 原样。

当你看到 slot 1 被调用时,然后在 slot 1 完成之前调用 slot 2 的情况可能是因为你在 slot 1 中调用了一些函数调用另一个函数等和一些深入内部调用 signal 2 的函数,它发出 slot 2 的调用,只需在调试器中设置断点并找出发生了什么。

在不与其他线程协作的单个 GUI 线程中使用任何形式的锁定是没有意义的。 GUI 线程中的所有内容都已为您序列化。你正在解决一个想象中的问题。只要您的代码在执行,就不会在同一线程中在您的背后执行任何其他代码。

onZoom-slot is called and before it is finished it is calling the onMouseMoved-slot

这是错误的。您显示的代码中没有发生任何事情。也许 ui.gVPPI->scale() 正在重新进入事件循环。您确实需要展示一个独立的示例:您的问题出在您没有展示的代码中。

How can I prevent the slots to interrupt each other

已经为您阻止。你什么都不用做。

in my original program used by another thread

一般来说,GUI线程中的对象不能有直接从其他线程调用的方法。

大多数时候,在一个问题上抛出第二个线程会让你遇到两个问题。正确设计的非阻塞代码将正常工作,即使可能有点慢,即使在单个线程中 运行 也是如此。那将是你的出发点 - 然后你将对象移动到其他线程只是为了分散工作。不过,理想情况下,异步处理应该通过 运行 通过 QtConcurrent::run.

异步工作来完成

我设法设置了我的调试器,问题似乎是 QGraphicsView::scale() 直接在内部调用 QgraphicsScene::mouseMoveEvent()。所以我需要引入一个变量来告诉 mouseMoveEvent,它是从 QGraphicsView::scale() 还是从物理鼠标移动调用的。

protected slots:
    void onZoom(QWheelEvent *event)
    {
        qDebug() << "Zoom lock" << QThread::currentThreadId();
        mutex.lock();
        qDebug() << "Zoom locked";

        scale = true;
            if(event->delta() > 0)
                gVPPI->scale(1.01, 1.01);
            else
                gVPPI->scale(1/1.01, 1/1.01);
        scale = false;

        qDebug() << "Zoom unlock";
        mutex.unlock();
        qDebug() << "Zoom unlocked";
    };

    void onMouseMoved(QGraphicsSceneMouseEvent *event)
    {
        if(scale == false)
        {
            qDebug() << "Move lock" << QThread::currentThreadId();
            mutex.lock();
            qDebug() << "move locked";
        }

        test->setPos(test->pos()+event->scenePos()-event->lastScenePos());

        if(scale == false)
        {
            qDebug() << "Move unlock";
            mutex.unlock();
            qDebug() << "Move unlocked";
        }
    };