gpt4 book ai didi

python - 派生类在 PySide (Qt/PyQt) 的错误线程中接收信号

转载 作者:行者123 更新时间:2023-11-28 16:28:52 24 4
gpt4 key购买 nike

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

示例(一些信号调试代码改编自 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]
...

这向我表明派生对象在 moveToThread() 期间被正确传输到一个新线程(名义上,用于 Qt 事件处理),但随后在主线程上启动(并接收)以某种方式线程。

附加:它适用于 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() 装饰器即可正常工作。

所以这似乎是一个与 Slot 装饰器相关的 PySide 错误。

非常感谢!

最佳答案

在 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

关于python - 派生类在 PySide (Qt/PyQt) 的错误线程中接收信号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34125065/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com