2015-12-07 39 views
2

我在派生類接收PySide中的信號時遇到了問題。我在主(GUI或命令行應用程序)線程的兩個獨立線程上使用發送器和接收器。線程是QThread對象。發送器和接收器使用QObject.moveToThread()創建後立即移動到它們的線程。如果接收器是直接從QObject派生的,則所有工作正常,並且接收器在其線程中接收。但是,如果接收器是從派生自QObject的基類派生的,接收器仍然會收到該信號,但是在錯誤的線程(主線程)上會這樣做。在PySide(Qt/PyQt)中接收錯誤線程信號的派生類

(有適於從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

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

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] 

......區別在於Base類在其自己的線程上接收,而Derived類在MainThread上接收。

有誰知道爲什麼?非常感謝!

軟件:PySide版本:1.2.4; QtCore版本:4.8.6; Ubuntu 14.04; Python 3.4.4。

繼@ 101的評論:

的信號覆蓋是沒有必要的失敗。這些派生類也不能(在被稱爲在錯誤的線程的意義上):

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()失敗了派生對象。然而,這似乎並沒有這樣的情況:

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] 
... 

這表明,我認爲派生的對象被正確地轉移到一個新的線程(理論上,Qt的事件處理)在moveToThread()期間,但隨後以某種方式啓動(並接收)主線程。

附加:在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] 

確認@ 101的在Python 3

結果正如下面描述。所有工作都很好,只需刪除所有@Slot()裝飾器。

所以它似乎是一個與插槽裝飾器相關的PySide錯誤。

非常感謝!

+0

這會讓如果差'receive'在'Derived'沒有覆蓋在'Base'? – 101

+0

謝謝 - 更多信息添加到上面的問題。 –

+0

感謝@ 101的回答,我現在發現了一個關於這個問題的開放bug報告:https://bugreports.qt.io/browse/PYSIDE-249 –

回答

1

在Windows上使用Python 2.7.10和PySide 1.2.2我做了一個類似的例子,發現了同樣的問題。是的,當連接到派生類時,代碼實際上似乎卡在主線程中(我通過阻止主線程來顯示監聽器不再響應來檢查此代碼)。下面是我所使用的最小的例子:

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_()) 

我發現幾種替代方法:

  • 從基類去除@QtCore.Slot裝飾(它通常是不必要的反正)
  • 添加未使用的參數傳遞給基類的@QtCore.Slot裝飾者,例如@QtCore.Slot(int),但前提是參數實際上沒有作爲參數傳遞給方法。也許增加這個虛擬參數基本上使裝飾器無效。
  • 使用PyQt4的

所以,是的,它似乎繼承已有一個裝飾定義的插槽中的類不能被正確地移動到一個線程。我也很想知道爲什麼這是。

PySide錯誤的位置:https://bugreports.qt.io/browse/PYSIDE-249