Issue
I have a Python3 multithreaded application that uses PySide.
Python version: 3.4
PySide version: 1.2.4
I set up a signaller thread that has multiple signals. I also have a receiver object running in a different thread (using QObject and moveToThread to ensure that all code actually runs in the established thread).
Within the receiver, I set up MORE than one connection to the signals in the signaller thread. But when I start things up, only the last connection I make is correctly connected. The other connections end up making the wrong signal-slot correlations.
I've tried different things. One example is the code below. I've also tried using queue.Queue to send the connection request from the receiver to the signaller so that the connection is actually made in the signaller thread, but that doesn't look as if it made any difference.
My question has two parts:
- How do I ensure the correct signal-slot pairing when connecting multiple signal-slots from different threads?
- Why the heck is this happening?
Code:
#!/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_()
What I expect is that when signal 2 is emitted, it gets directed to the slot I have connected. Likewise for signal 9.
What actually happens is (your mileage may vary, I suspect that it's because something isn't actually thread-safe although PySide/Qt docs suggest that connecting is threadsafe)
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>
Note that I print the addresses of the signals, and the address of key 9 matches when I make the connection and do the emit (as well as in the slot), but the "other" signal connected to the slot for key 2 (here, the signal associated with key 5) doesn't match the address for the signal I attempted connection with.
Solution
Short Answer:
signals need to be declared inside the class, assigning them later is not really supported*.
*it seems to work in PySide, but it picks the wrong signal to connect or emit. In PyQt it fails immediately when trying to connect or emit such a signal.
Longer Answer:
QObject
uses a metaclass (Shiboken.ObjectType
), which is used to create the class instances when one is defined, and at that time the class is inspected to be set up correctly. So when you add signals later, then they won't be available at that point of time, so they can't be set up correctly.
If you want to dynamically assign signals a way would be to create a custom metaclass derived from ObjectType which then can add the necessary information before the actual class is created.
Example:
...
# 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_()
...
Answered By - mata
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.