派生类在PySide(Qt/PyQt)中的错误线程中接收信号 [英] Derived classes receiving signals in wrong thread in PySide (Qt/PyQt)

查看:99
本文介绍了派生类在PySide(Qt/PyQt)中的错误线程中接收信号的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在派生类在PySide中正确接收信号方面遇到问题.我正在从主线程(GUI或命令行应用程序)的两个独立线程上使用发送器和接收器.线程是QThread对象.发送器和接收器在创建后立即使用QObject.moveToThread()移至其线程.如果接收方直接从QObject派生,则一切正常,并且接收方在其线程内接收.但是,如果接收器是从QObject派生的基类派生的,则接收器仍会接收信号,但是会在错误的线程(主线程)上接收信号.

I'm having problems getting a derived class to receive signals properly in PySide. I'm using a transmitter and a receiver on two separate threads from the main (GUI or command-line application) thread. The threads are QThread objects. The transmitter and receiver are moved immediately after creation to their thread using QObject.moveToThread(). If the receiver is derived directly from QObject, all works fine, and the receiver receives within its thread. However, if the receiver is derived from a base class that is derived from QObject, the receiver still receives the signal, but does so on the wrong thread (the main thread).

示例(其中一些信号调试代码改编自 PyQt& unittest-测试信号和插槽 ):

Example (with some signal debugging code adapted from PyQt & unittest - Testing signal and slots):

#!/usr/bin/env python3
# weigh/bugtest_qt_signal_derived.py

import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
import sys
import threading
import time

from PySide import QtCore
from PySide.QtCore import (
    QCoreApplication,
    QObject,
    QThread,
    Signal,
    Slot,
)

_oldEmit = QtCore.QObject.emit  # normal method


def debug_emit(self, *args):
    logger.debug("EMIT: thread name={}, emit args={}".format(
        threading.current_thread().name,
        repr(args),
    ))
    _oldEmit(self, *args)

QtCore.QObject.emit = debug_emit


def report(msg):
    logger.info("{} [{}]".format(msg, threading.current_thread().name))


class Transmitter(QObject):
    transmit = Signal()
    finished = Signal()

    def start(self):
        count = 3
        logger.info("Starting transmitter")
        while count > 0:
            time.sleep(1)  # seconds
            report("transmitting, count={}".format(count))
            self.transmit.emit()
            count -= 1
        logger.info("Stopping transmitter")
        self.finished.emit()


class Base(QObject):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

    @Slot()
    def start(self):
        report("Starting receiver")

    @Slot()
    def receive(self):
        report("receive: BASE")


class Derived(Base):
    def __init__(self, parent=None):
        super().__init__(parent=parent)

    @Slot()
    def receive(self):
        report("receive: DERIVED")


USE_DERIVED = True

if __name__ == '__main__':
    logging.basicConfig()
    logger.setLevel(logging.DEBUG)

    # Objects
    app = QCoreApplication(sys.argv)

    tx_thread = QThread()
    transmitter = Transmitter()
    transmitter.moveToThread(tx_thread)

    rx_thread = QThread()
    if USE_DERIVED:
        receiver = Derived()
    else:
        receiver = Base()
    receiver.moveToThread(rx_thread)

    # Signals: startup
    tx_thread.started.connect(transmitter.start)
    rx_thread.started.connect(receiver.start)
    # ... shutdown
    transmitter.finished.connect(tx_thread.quit)
    tx_thread.finished.connect(rx_thread.quit)
    rx_thread.finished.connect(app.quit)
    # ... action
    transmitter.transmit.connect(receiver.receive)

    # Go
    rx_thread.start()
    tx_thread.start()
    report("Starting app")
    app.exec_()

带有USE_DERIVED = False的输出:

Output with USE_DERIVED = False:

INFO:__main__:Starting app [MainThread]
INFO:__main__:Starting receiver [Dummy-1]
INFO:__main__:Starting transmitter
INFO:__main__:transmitting, count=3 [Dummy-2]
DEBUG:__main__:EMIT: thread name=Dummy-2, emit args=('2transmit()',)
INFO:__main__:receive: BASE [Dummy-1]
INFO:__main__:transmitting, count=2 [Dummy-2]
DEBUG:__main__:EMIT: thread name=Dummy-2, emit args=('2transmit()',)
INFO:__main__:receive: BASE [Dummy-1]
INFO:__main__:transmitting, count=1 [Dummy-2]
DEBUG:__main__:EMIT: thread name=Dummy-2, emit args=('2transmit()',)
INFO:__main__:Stopping transmitter
DEBUG:__main__:EMIT: thread name=Dummy-2, emit args=('2finished()',)
INFO:__main__:receive: BASE [Dummy-1]

带有USE_DERIVED = True的输出:

Output with USE_DERIVED = True:

INFO:__main__:Starting app [MainThread]
INFO:__main__:Starting receiver [MainThread]
INFO:__main__:Starting transmitter
INFO:__main__:transmitting, count=3 [Dummy-1]
DEBUG:__main__:EMIT: thread name=Dummy-1, emit args=('2transmit()',)
INFO:__main__:receive: DERIVED [MainThread]
INFO:__main__:transmitting, count=2 [Dummy-1]
DEBUG:__main__:EMIT: thread name=Dummy-1, emit args=('2transmit()',)
INFO:__main__:receive: DERIVED [MainThread]
INFO:__main__:transmitting, count=1 [Dummy-1]
DEBUG:__main__:EMIT: thread name=Dummy-1, emit args=('2transmit()',)
INFO:__main__:Stopping transmitter
DEBUG:__main__:EMIT: thread name=Dummy-1, emit args=('2finished()',)
INFO:__main__:receive: DERIVED [MainThread]

...区别在于,基类在其自己的线程上接收,而派生类在MainThread上接收.

... the difference being that the Base class receives on its own thread, and the Derived class receives on the MainThread.

有人知道为什么吗?非常感谢!

Does anyone know why? Many thanks!

软件:PySide版本:1.2.4; QtCore版本:4.8.6; Ubuntu 14.04; Python 3.4.4.

进一步@ 101的评论:

故障不需要信号超控.这些派生类也会失败(在错误的线程中被调用):

The signal override isn't necessary for failure. These derived classes also fail (in the sense of being called in the wrong thread):

class DerivedTwo(Base):
    def __init__(self, parent=None):
        super().__init__(parent=parent)


class DerivedThree(Base):
    def __init__(self, parent=None):
        QObject.__init__(self, parent=parent)

由于输出表明派生的接收器对象是在错误的线程上启动的,所以我想知道问题是否在于QObject.moveToThread()对于派生的对象失败.但是,情况似乎并非如此:

Since the output suggests the derived receiver object is starting on the wrong thread, I wondered if the problem is that QObject.moveToThread() fails for derived objects. However, that doesn't seem to be the case:

def debug_object(obj):
    logger.debug("Object {} belongs to QThread {}".format(obj, obj.thread()))

def debug_thread(thread_name, thread):
    logger.debug("{} is QThread {}".format(thread_name, thread))

# ...

tx_thread = QThread()
debug_thread("tx_thread", tx_thread)
transmitter = Transmitter()
debug_object(transmitter)
transmitter.moveToThread(tx_thread)
debug_object(transmitter)

rx_thread = QThread()
debug_thread("rx_thread", rx_thread)
receiver = DerivedTwo()
debug_object(receiver)
receiver.moveToThread(rx_thread)
debug_object(receiver)

给予

DEBUG:__main__:tx_thread is QThread <PySide.QtCore.QThread object at 0x7fc4a3befd08>
DEBUG:__main__:Object <__main__.Transmitter object at 0x7fc4a3bf2648> belongs to QThread <PySide.QtCore.QThread object at 0x7fc4a3bf2688>
DEBUG:__main__:Object <__main__.Transmitter object at 0x7fc4a3bf2648> belongs to QThread <PySide.QtCore.QThread object at 0x7fc4a3befd08>
DEBUG:__main__:rx_thread is QThread <PySide.QtCore.QThread object at 0x7fc4a3bf2708>
DEBUG:__main__:Object <__main__.DerivedTwo object at 0x7fc4a3bf2788> belongs to QThread <PySide.QtCore.QThread object at 0x7fc4a3bf2688>
DEBUG:__main__:Object <__main__.DerivedTwo object at 0x7fc4a3bf2788> belongs to QThread <PySide.QtCore.QThread object at 0x7fc4a3bf2708>
INFO:__main__:Starting app [MainThread]
INFO:__main__:Starting receiver [MainThread]
INFO:__main__:Starting transmitter [Dummy-1]
INFO:__main__:transmitting, count=3 [Dummy-1]
DEBUG:__main__:EMIT: thread name=Dummy-1, emit args=('2transmit()',)
INFO:__main__:receive: BASE [MainThread]
...

在我看来,派生对象在moveToThread()期间已正确地转移到新线程(名义上,用于Qt事件处理),然后以某种方式在主线程上启动(并接收).

which suggests to me that the derived object gets transferred correctly to a new thread (notionally, for Qt events processing) during moveToThread(), but then is started on (and receives on) the main thread somehow.

其他:它可以在C ++ Qt中使用

标题:

// bugtest_qt_signal_derived.h

#include <QtCore/QCoreApplication>
#include <QtCore/QtDebug>  // not QDebug
#include <QtCore/QObject>
#include <QtCore/QString>  // works with qDebug where std::string doesn't
#include <QtCore/QThread>

void debug_object(const QString& obj_name, const QObject& obj);
void debug_thread(const QString& thread_name, const QThread& thread);
void report(const QString& msg);

class Transmitter : public QObject
{
    Q_OBJECT  // enables macros like "signals:", "slots:", "emit"
public:
    Transmitter() {}
    virtual ~Transmitter() {}
signals:
    void transmit();
    void finished();
public slots:
    void start();
};

class Base : public QObject
{
    Q_OBJECT
public:
    Base() {}
public slots:
    void start();
    void receive();
};

class Derived : public Base
{
    Q_OBJECT
public:
    Derived() {}
public slots:
    void receive();
};

来源:

// bugtest_qt_signal_derived.cpp

#include "bugtest_qt_signal_derived.h"
#include <unistd.h>  // for sleep()

void debug_object(const QString& obj_name, const QObject& obj)
{
    qDebug() << "Object" << obj_name << "belongs to QThread" << obj.thread();
}

void debug_thread(const QString& thread_name, const QThread& thread)
{
    qDebug() << thread_name << "is QThread at" << &thread;
}

void report(const QString& msg)
{
    qDebug().nospace() << msg << " [" << QThread::currentThreadId() << "]";
}

void Transmitter::start()
{
    unsigned int count = 3;
    report("Starting transmitter");
    while (count > 0) {
        sleep(1);  // seconds
        report(QString("transmitting, count=%1").arg(count));
        emit transmit();
        count -= 1;
    }
    report("Stopping transmitter");
    emit finished();
}

void Base::start()
{
    report("Starting receiver");
}

void Base::receive()
{
    report("receive: BASE");
}

void Derived::receive()
{
    report("receive: DERIVED");
}

#define USE_DERIVED

int main(int argc, char* argv[])
{
    // Objects
    QCoreApplication app(argc, argv);

    QThread tx_thread;
    debug_thread("tx_thread", tx_thread);
    Transmitter transmitter;
    debug_object("transmitter", transmitter);
    transmitter.moveToThread(&tx_thread);
    debug_object("transmitter", transmitter);

    QThread rx_thread;
    debug_thread("rx_thread", rx_thread);
#ifdef USE_DERIVED
    Derived receiver;
#else
    Base receiver;
#endif
    debug_object("receiver", receiver);
    receiver.moveToThread(&rx_thread);
    debug_object("receiver", receiver);

    // Signals: startup
    QObject::connect(&tx_thread, SIGNAL(started()),
                     &transmitter, SLOT(start()));    
    QObject::connect(&rx_thread, SIGNAL(started()),
                     &receiver, SLOT(start()));    
    // ... shutdown
    QObject::connect(&transmitter, SIGNAL(finished()),
                     &tx_thread, SLOT(quit()));    
    QObject::connect(&tx_thread, SIGNAL(finished()),
                     &rx_thread, SLOT(quit()));    
    QObject::connect(&rx_thread, SIGNAL(finished()),
                     &app, SLOT(quit()));    
    // ... action
    QObject::connect(&transmitter, SIGNAL(transmit()),
                     &receiver, SLOT(receive()));    

    // Go
    rx_thread.start();
    tx_thread.start();
    report("Starting app");
    return app.exec();
}

输出:

"tx_thread" is QThread at QThread(0x7ffc138c5330) 
Object "transmitter" belongs to QThread QThread(0xdae1e0) 
Object "transmitter" belongs to QThread QThread(0x7ffc138c5330) 
"rx_thread" is QThread at QThread(0x7ffc138c5350) 
Object "receiver" belongs to QThread QThread(0xdae1e0) 
Object "receiver" belongs to QThread QThread(0x7ffc138c5350) 
"Starting app" [0x7f032fb32780]
"Starting transmitter" [0x7f032ae77700]
"Starting receiver" [0x7f032b678700]
"transmitting, count=3" [0x7f032ae77700]
"receive: DERIVED" [0x7f032b678700]
"transmitting, count=2" [0x7f032ae77700]
"receive: DERIVED" [0x7f032b678700]
"transmitting, count=1" [0x7f032ae77700]
"Stopping transmitter" [0x7f032ae77700]
"receive: DERIVED" [0x7f032b678700]

其他:它也可以在PyQt中使用

代码:

#!/usr/bin/env python2

import logging
logger = logging.getLogger(__name__)
logger.addHandler(logging.NullHandler())
import sys
import threading
import time

from PyQt4.QtCore import (
    QCoreApplication,
    QObject,
    QThread,
    pyqtSignal,
    pyqtSlot,
)


def debug_object(obj):
    logger.debug("Object {} belongs to QThread {}".format(obj, obj.thread()))


def debug_thread(thread_name, thread):
    logger.debug("{} is QThread {}".format(thread_name, thread))


def report(msg):
    logger.info("{} [{}]".format(msg, threading.current_thread().name))


class Transmitter(QObject):
    transmit = pyqtSignal()
    finished = pyqtSignal()

    def start(self):
        count = 3
        report("Starting transmitter")
        while count > 0:
            time.sleep(1)  # seconds
            report("transmitting, count={}".format(count))
            self.transmit.emit()
            count -= 1
        report("Stopping transmitter")
        self.finished.emit()


class Base(QObject):
    def __init__(self, parent=None):
        super(Base, self).__init__(parent=parent)

    @pyqtSlot()
    def start(self):
        report("Starting receiver")

    @pyqtSlot()
    def receive(self):
        report("receive: BASE")


class Derived(Base):
    def __init__(self, parent=None):
        super(Derived, self).__init__(parent=parent)

    @pyqtSlot()
    def receive(self):
        report("receive: DERIVED")


USE_DERIVED = True

if __name__ == '__main__':
    logging.basicConfig()
    logger.setLevel(logging.DEBUG)

    # Objects
    app = QCoreApplication(sys.argv)

    tx_thread = QThread()
    debug_thread("tx_thread", tx_thread)
    transmitter = Transmitter()
    debug_object(transmitter)
    transmitter.moveToThread(tx_thread)
    debug_object(transmitter)

    rx_thread = QThread()
    debug_thread("rx_thread", rx_thread)
    if USE_DERIVED:
        receiver = Derived()
    else:
        receiver = Base()
    debug_object(receiver)
    receiver.moveToThread(rx_thread)
    debug_object(receiver)

    # Signals: startup
    tx_thread.started.connect(transmitter.start)
    rx_thread.started.connect(receiver.start)
    # ... shutdown
    transmitter.finished.connect(tx_thread.quit)
    tx_thread.finished.connect(rx_thread.quit)
    rx_thread.finished.connect(app.quit)
    # ... action
    transmitter.transmit.connect(receiver.receive)

    # Go
    rx_thread.start()
    tx_thread.start()
    report("Starting app")
    app.exec_()

输出:

DEBUG:__main__:tx_thread is QThread <PyQt4.QtCore.QThread object at 0x7fd0b7ad0770>
DEBUG:__main__:Object <__main__.Transmitter object at 0x7fd0b7ad0808> belongs to QThread <PyQt4.QtCore.QThread object at 0x7fd0b7ad08a0>
DEBUG:__main__:Object <__main__.Transmitter object at 0x7fd0b7ad0808> belongs to QThread <PyQt4.QtCore.QThread object at 0x7fd0b7ad0770>
DEBUG:__main__:rx_thread is QThread <PyQt4.QtCore.QThread object at 0x7fd0b7ad08a0>
DEBUG:__main__:Object <__main__.Derived object at 0x7fd0b7ad0938> belongs to QThread <PyQt4.QtCore.QThread object at 0x7fd0b7ad09d0>
DEBUG:__main__:Object <__main__.Derived object at 0x7fd0b7ad0938> belongs to QThread <PyQt4.QtCore.QThread object at 0x7fd0b7ad08a0>
INFO:__main__:Starting app [MainThread]
INFO:__main__:Starting transmitter [Dummy-1]
INFO:__main__:Starting receiver [Dummy-2]
INFO:__main__:transmitting, count=3 [Dummy-1]
INFO:__main__:receive: DERIVED [Dummy-2]
INFO:__main__:transmitting, count=2 [Dummy-1]
INFO:__main__:receive: DERIVED [Dummy-2]
INFO:__main__:transmitting, count=1 [Dummy-1]
INFO:__main__:Stopping transmitter [Dummy-1]
INFO:__main__:receive: DERIVED [Dummy-2]

在Python 3中确认@ 101的发现

如下所述.只需删除所有@Slot()装饰器,一切都可以正常工作.

Just as described below. All works fine just by removing all @Slot() decorators.

因此,这似乎是与Slot装饰器有关的PySide错误.

So it seems to be a PySide bug relating to the Slot decorator.

非常感谢!

推荐答案

在Windows上使用Python 2.7.10和PySide 1.2.2,我做了一个类似的示例,并发现了相同的问题.是的,当连接到派生类时,代码实际上似乎卡在了主线程中(我通过阻塞主线程来进行检查以表明侦听器不再响应).这是我使用的最小示例:

Using Python 2.7.10 and PySide 1.2.2 on Windows I made a similar example and found the same issue. And yes, when connecting to the derived class the code does actually seem to be stuck in the main thread (I checked this by blocking the main thread to show that the listener no longer responds). Here's the minimal example I used:

from PySide import QtCore, QtGui
import threading, time, sys

class Signaller(QtCore.QObject):
    signal = QtCore.Signal()
    def send_signals(self):
        while True:
            self.signal.emit()
            time.sleep(1)

class BaseListener(QtCore.QObject):
    @QtCore.Slot()
    def on_signal(self):
        print 'Got signal in', threading.current_thread().name

class DerivedListener(BaseListener):
    pass

class App(QtGui.QApplication):
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)

        # self.listener = BaseListener()
        self.listener = DerivedListener()
        self.listener_thread = QtCore.QThread()
        self.listener.moveToThread(self.listener_thread)

        self.signaller = Signaller()
        self.signaller_thread = QtCore.QThread()
        self.signaller.moveToThread(self.signaller_thread)
        self.signaller.signal.connect(self.listener.on_signal)
        self.signaller_thread.started.connect(self.signaller.send_signals)

        self.listener_thread.start()
        self.signaller_thread.start()

sys.exit(App(sys.argv).exec_())

我找到了几种解决方法:

I found several workarounds:

  • 从基类中删除@QtCore.Slot装饰器(反正通常是不必要的)
  • 将未使用的参数添加到基类的@QtCore.Slot装饰器中,例如@QtCore.Slot(int),但仅当参数未作为参数实际传递给方法时.也许添加此虚拟参数实际上会使装饰器无效.
  • 使用PyQt4
  • remove the @QtCore.Slot decorator from the base class (it's generally unnecessary anyway)
  • adding an unused argument to the base class's @QtCore.Slot decorator, e.g. @QtCore.Slot(int), but only if the argument is not actually passed as an argument to the method. Perhaps adding this dummy argument essentially invalidates the decorator.
  • use PyQt4

所以是的,似乎无法将已经具有用装饰器定义的插槽的类的子类正确地移动到线程中.我也很好奇为什么会这样.

So yes, it seems that subclassing a class that already has a slot defined with a decorator cannot be properly moved to a thread. I'm also curious to know why exactly this is.

此处的PySide错误:: https://bugreports.qt. io/browse/PYSIDE-249

这篇关于派生类在PySide(Qt/PyQt)中的错误线程中接收信号的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆