PySide:多线程信号连接将插槽连接到错误的信号
PySide: Multithread signal connection connects slot to the wrong signal
我有一个 Python3 使用 PySide 的多线程应用程序。
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 的地址匹配,但是 "other" 信号连接到键 2 的插槽(这里是与键 5 关联的信号)与我尝试连接的信号的地址不匹配。
简答:
需要在 class 中声明信号,实际上不支持稍后分配它们*。
*它似乎在 PySide 中工作,但它选择了错误的信号来连接或发射。在 PyQt 中,尝试连接或发出此类信号时会立即失败。
更长的答案:
QObject
使用元class (Shiboken.ObjectType
),它用于在定义一个时创建 class 个实例,那时 class 被检查是否设置正确。因此,当您稍后添加信号时,它们在那个时间点将不可用,因此无法正确设置它们。
如果您想动态分配信号,一种方法是创建一个派生自 ObjectType 的自定义元class,然后可以在创建实际 class 之前添加必要的信息。
示例:
...
# 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_()
...
我有一个 Python3 使用 PySide 的多线程应用程序。
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 的地址匹配,但是 "other" 信号连接到键 2 的插槽(这里是与键 5 关联的信号)与我尝试连接的信号的地址不匹配。
简答:
需要在 class 中声明信号,实际上不支持稍后分配它们*。
*它似乎在 PySide 中工作,但它选择了错误的信号来连接或发射。在 PyQt 中,尝试连接或发出此类信号时会立即失败。
更长的答案:
QObject
使用元class (Shiboken.ObjectType
),它用于在定义一个时创建 class 个实例,那时 class 被检查是否设置正确。因此,当您稍后添加信号时,它们在那个时间点将不可用,因此无法正确设置它们。
如果您想动态分配信号,一种方法是创建一个派生自 ObjectType 的自定义元class,然后可以在创建实际 class 之前添加必要的信息。
示例:
...
# 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_()
...