我有一个使用PySide的Python3多线程应用程序。
Python版本:3.4
PySide版本:1.2.4
我设置了一个有多个信号的信号器线程。我还有一个在不同线程中运行的接收器对象(使用QObject和moveToThread来确保所有代码实际在已建立的线程中运行)。
在接收器中,我设置了多个与信号器线程中信号的连接。但是当我开始时,只有我做的最后一个连接才能正确连接。其他连接最终导致错误的信号槽相关性。
我尝试了不同的东西。一个例子是下面的代码。我还尝试使用queue.Queue将连接请求从接收器发送到信号器,以便实际在信号器线程中建立连接,但这看起来并没有任何区别。
我的问题分为两部分:
代码:
#!/usr/bin/env python3
from PySide import QtCore
import signal # just so that we can do ctrl-c
import time
class Information:
def __init__(self, key, sig):
self.key = str(key)
self.sig = str(sig)
def __str__(self):
return 'Key {} for Signal {}'.format(self.key, self.sig)
class Signaller(QtCore.QThread):
sig_dict = {0: None,
1: None,
2: None,
3: None,
4: None,
5: None,
6: None,
7: None,
8: None,
9: None}
def __init__(self, parent=None):
super().__init__(parent)
# Copy the discrete signals to the sig_dict dictionary
for k in list(self.sig_dict.keys()):
self.sig_dict[k] = getattr(self, 'signal_{}'.format(k))
def run(self):
for key, sig in self.sig_dict.items():
print("Emitting Key {}: Signal {}".format(key, sig))
sig.emit(Information(key, sig))
self.exec_()
# We need to explicitly set attributes in the Signaller class
# to represent the signals because the metaclass only checks
# direct attributes when binding signals
for k in Signaller.sig_dict:
setattr(Signaller, 'signal_{}'.format(k), QtCore.Signal(Information))
class Receiver(QtCore.QObject):
start_signal = QtCore.Signal()
def __init__(self, signaller, parent=None):
super().__init__(parent)
self.start_signal.connect(self.StartReceiving)
self.signaller = signaller
def StartReceiving(self):
print("Connecting key 2 to signal {}".format(self.signaller.sig_dict[2]), flush=True)
self.signaller.sig_dict[2].connect(self.ReceiveSignal2)
print("Connecting key 9 to signal {}".format(self.signaller.sig_dict[9]), flush=True)
self.signaller.sig_dict[9].connect(self.ReceiveSignal9)
def ReceiveSignal2(self, info):
print(info)
def ReceiveSignal9(self, info):
print(info)
signal.signal(signal.SIGINT, signal.SIG_DFL)
app = QtCore.QCoreApplication([])
signaller = Signaller()
receiver = Receiver(signaller)
thread = QtCore.QThread()
receiver.moveToThread(thread)
thread.start()
receiver.start_signal.emit()
#Trivial pause to provide time for receiver to set up connections
time.sleep(1)
signaller.start()
app.exec_()
我期望的是,当发出信号2时,它会被引导到我连接的插槽。同样对于信号9。
实际发生的是(你的里程可能会有所不同,我怀疑这是因为某些东西实际上不是线程安全的,尽管PySide / Qt文档建议连接是线程安全的)
C:\temp> pyside_signal_example.py
Connecting key 2 to signal <PySide.QtCore.SignalInstance object at 0x02C20C80>
Connecting key 9 to signal <PySide.QtCore.SignalInstance object at 0x02C20C50>
Emitting Key 0: Signal <PySide.QtCore.SignalInstance object at 0x02C20CA0>
Emitting Key 1: Signal <PySide.QtCore.SignalInstance object at 0x02C20CB0>
Emitting Key 2: Signal <PySide.QtCore.SignalInstance object at 0x02C20C80>
Emitting Key 3: Signal <PySide.QtCore.SignalInstance object at 0x02C20CE0>
Emitting Key 4: Signal <PySide.QtCore.SignalInstance object at 0x02C20CD0>
Emitting Key 5: Signal <PySide.QtCore.SignalInstance object at 0x02C20C70>
Emitting Key 6: Signal <PySide.QtCore.SignalInstance object at 0x02C20C90>
Key 5 for Signal <PySide.QtCore.SignalInstance object at 0x02C20C70>
Emitting Key 7: Signal <PySide.QtCore.SignalInstance object at 0x02C20C60>
Emitting Key 8: Signal <PySide.QtCore.SignalInstance object at 0x02C20CC0>
Emitting Key 9: Signal <PySide.QtCore.SignalInstance object at 0x02C20C50>
Key 9 for Signal <PySide.QtCore.SignalInstance object at 0x02C20C50>
请注意,我打印信号的地址,当我进行连接并执行发射(以及插槽中)时,键9的地址匹配,但连接到插槽的“其他”信号用于键2(这里,与键5相关的信号)与我尝试连接的信号的地址不匹配。
答案 0 :(得分:1)
简答:
信号需要在类中声明,稍后分配它们并不是真正支持*。
*它似乎在PySide中工作,但它选择错误的信号来连接或发射。在PyQt中,它在尝试连接或发出这样的信号时立即失败。
更长的答案:
QObject
使用元类(Shiboken.ObjectType
),用于在定义类实例时创建类实例,并且在此时检查类是否正确设置。因此,当您稍后添加信号时,它们将不会在那个时间点可用,因此无法正确设置它们。
如果要动态分配信号,可以创建一个从ObjectType派生的自定义元类,然后可以在创建实际类之前添加必要的信息。
示例:
...
# ObjectType is not directly accessible, so need to get as QObject's type
ObjectType = type(QtCore.QObject)
class SignallerMeta(ObjectType):
def __new__(cls, name, parents, dct):
sig_dct = {} # also generate the sig_dict class attribute here
for i in range(10):
signal = QtCore.Signal(Information)
sig_dct[i] = signal
dct['signal_{}'.format(i)] = signal
dct['sig_dict'] = sig_dct
return super().__new__(cls, name, parents, dct)
class Signaller(QtCore.QThread, metaclass=SignallerMeta):
def __init__(self, parent=None):
super().__init__(parent)
# no need to copy the signals here
def run(self):
for key, sig in self.sig_dict.items():
print("Emitting Key {}: Signal {}".format(key, sig))
sig.emit(Information(key, sig))
self.exec_()
...