PySide:多线程信号连接将插槽连接到错误的信号

时间:2015-12-03 20:06:38

标签: multithreading python-3.x pyside

我有一个使用PySide的Python3多线程应用程序。

Python版本:3.4

PySide版本:1.2.4

我设置了一个有多个信号的信号器线程。我还有一个在不同线程中运行的接收器对象(使用QObject和moveToThread来确保所有代码实际在已建立的线程中运行)。

在接收器中,我设置了多个与信号器线程中信号的连接。但是当我开始时,只有我做的最后一个连接才能正确连接。其他连接最终导致错误的信号槽相关性。

我尝试了不同的东西。一个例子是下面的代码。我还尝试使用queue.Queue将连接请求从接收器发送到信号器,以便实际在信号器线程中建立连接,但这看起来并没有任何区别。

我的问题分为两部分:

  1. 当连接来自不同线程的多个信号槽时,如何确保正确的信号槽配对?
  2. 为什么会发生这种情况?
  3. 代码:

    #!/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相关的信号)与我尝试连接的信号的地址不匹配。

1 个答案:

答案 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_()
...