PySide: Multithread signal connection connects slot to the wrong signal - multithreading

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.

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

Related

wxpython postevent from coroutine to a parent window (modal)

I got a coroutine running to catch events sent by a footswitch device. This coroutine is launched from the main window as following.
class FootswitchMonitor(wx.Frame):
def __init__(self, parent, title):
super().__init__(parent, title=title,
size=(350, 150))
self.InitUI()
StartCoroutine(self.footswitch_callback, self)
self.Bind(EVT_FOOTSWITCH, self.pedal)
def on_footswitch(self, evt):
print(f"eid: {evt.GetId()} code: {evt.code} ")
async def footswitch_callback(self):
device = get_footswitch_device()
device.grab()
key_pressed = None
async for ev in device.async_read_loop():
if ev.type == 1 and ev.value == 1: # only key events and key_down
if key_pressed != ev.code:
event = footswitch_event(code=ev.code)
wx.PostEvent(self, event)
key_pressed = ev.code
else:
key_pressed = None
def setup_footswitch(self, event):
cDialog = ConfigDialog(None)
cDialog.ShowModal()
cDialog.Destroy()
In order to setup the device I use a ConfigDialog dialog box where I'd like to bind the same EVT_FOOTSWITCH.
class ConfigDialog(wx.Dialog):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.InitUI()
self.SetSize(350, 450)
self.SetTitle("Configuration")
self.Bind(EVT_FOOTSWITCH, self.footswitch_setup_callback)
And this is where it hurts. The main coroutine has FootswitchMonitor frame as target: wx.PostEvent(self, event). So the ConfigDialog windows won't get it.
I cannot run another coroutine with this device in the parent windows because it is grabed, which is locked by the main coroutine.
So my question is how can I catch the EVT_FOOTSWITCH in ConfigDialog a parent windows. Is it possible to post the event globaly? Is it possible to stop the main coroutine to run anotheone in the modal window?
I have something similar where I run reading the footpedal in a separate thread.
If the user wishes to reconfigure the pedal settings, I simply stop the thread, for the duration of the reconfiguration, then restart it.
Stopping the thread also ungrabs the device.
I do insist the program is restarted, as I only read the configuration of the footpedal's settings at program start. I found it less complicated this way.
Your other option, if you wish to attack it from a different angle, is to Unbind the event i.e. self.Unbind(EVT_FOOTSWITCH) and ungrab the device.
Then Bind it to your reconfiguration routine, which Unbind's when it finishes and finally rebind the event back to the original, whilst regrabbing the device, when you return to the main app.
My rather vast code can be perused at https://sourceforge.net/projects/footswitch2 , if you need further clues.
When browsing the file fs2.py search for PedalThread

Dart: Store heavy object in an isolate and access its method from main isolate without reinstatiating it

is it possible in Dart to instantiate a class in an isolate, and then send message to this isolate to receive a return value from its methods (instead of spawning a new isolate and re instantiate the same class every time)? I have a class with a long initialization, and heavy methods. I want to initialize it once and then access its methods without compromising the performance of the main isolate.
Edit: I mistakenly answered this question thinking python rather than dart. snakes on the brain / snakes on a plane
I am not familiar with dart programming, but it would seem the concurrency model has a lot of similarities (isolated memory, message passing, etc..). I was able to find an example of 2 way message passing with a dart isolate. There's a little difference in how it gets set-up, and the streams are a bit simpler than python Queue's, but in general the idea is the same.
Basically:
Create a port to receive data from the isolate
Create the isolate passing it the port it will send data back on
Within the isolate, create the port it will listen on, and send the other end of it back to main (so main can send messages)
Determine and implement a simple messaging protocol for remote method call on an object contained within the isolate.
This is basically duplicating what a multiprocessing.Manager class does, however it can be helpful to have a simplified example of how it can work:
from multiprocessing import Process, Lock, Queue
from time import sleep
class HeavyObject:
def __init__(self, x):
self._x = x
sleep(5) #heavy init
def heavy_method(self, y):
sleep(.2) #medium weight method
return self._x + y
def HO_server(in_q, out_q):
ho = HeavyObject(5)
#msg format for remote method call: ("method_name", (arg1, arg2, ...), {"kwarg1": 1, "kwarg2": 2, ...})
#pass None to exit worker cleanly
for msg in iter(in_q.get, None): #get a remote call message from the queue
out_q.put(getattr(ho, msg[0])(*msg[1], **msg[2])) #call the method with the args, and put the result back on the queue
class RMC_helper: #remote method caller for convienience
def __init__(self, in_queue, out_queue, lock):
self.in_q = in_queue
self.out_q = out_queue
self.l = lock
self.method = None
def __call__(self, *args, **kwargs):
if self.method is None:
raise Exception("no method to call")
with self.l: #isolate access to queue so results don't pile up and get popped off in possibly wrong order
print("put to queue: ", (self.method, args, kwargs))
self.in_q.put((self.method, args, kwargs))
return self.out_q.get()
def __getattr__(self, name):
if not name.startswith("__"):
self.method = name
return self
else:
super().__getattr__(name)
def child_worker(remote):
print("child", remote.heavy_method(5)) #prints 10
sleep(3) #child works on something else
print("child", remote.heavy_method(2)) #prints 7
if __name__ == "__main__":
in_queue = Queue()
out_queue = Queue()
lock = Lock() #lock is used as to not confuse which reply goes to which request
remote = RMC_helper(in_queue, out_queue, lock)
Server = Process(target=HO_server, args=(in_queue, out_queue))
Server.start()
Worker = Process(target=child_worker, args=(remote, ))
Worker.start()
print("main", remote.heavy_method(3)) #this will *probably* start first due to startup time of child
Worker.join()
with lock:
in_queue.put(None)
Server.join()
print("done")

Python's asyncio.Event() across different classes

I'm writing a Python program to interact with a device based on a CAN Bus. I'm using the python-can module successfully for this purpose. I'm also using asyncio to react to asynchronous events. I have written a "CanBusManager" class that is used by the "CanBusSequencer" class. The "CanBusManager" class takes care of generating/sending/receiving messages, and the CanBusSequencer drives the sequence of messages to be sent.
At some point in the sequence I want to wait until a specific message is received to "unlock" the remaining messages to be sent in the sequence. Overview in code:
main.py
async def main():
event = asyncio.Event()
sequencer = CanBusSequencer(event)
task = asyncio.create_task(sequencer.doSequence())
await task
asyncio.run(main(), debug=True)
canBusSequencer.py
from canBusManager import CanBusManager
class CanBusSequencer:
def __init__(self, event)
self.event = event
self.canManager = CanBusManager(event)
async def doSequence(self):
for index, row in self.df_sequence.iterrows():
if:...
self.canManager.sendMsg(...)
else:
self.canManager.sendMsg(...)
await self.event.wait()
self.event.clear()
canBusManager.py
import can
class CanBusManager():
def __init__(self, event):
self.event = event
self.startListening()
**EDIT**
def startListening(self):
self.msgNotifier = can.Notifier(self.canBus, self.receivedMsgCallback)
**EDIT**
def receivedMsgCallback(self, msg):
if(msg == ...):
self.event.set()
For now my program stays by the await self.event.wait(), even though the relevant message is received and the self.event.set() is executed. Running the program with debug = True reveals an
RuntimeError: Non-thread-safe operation invoked on an event loop other than the current one
that I don't really get. It has to do with the asyncio event loop, somehow not properly defined/managed. I'm coming from the C++ world and I'm currently writing my first large program with Python. Any guidance would be really appreciated:)
Your question doesn't explain how you arrange for receivedMsgCallback to be invoked.
If it is invoked by a classic "async" API which uses threads behind the scenes, then it will be invoked from outside the thread that runs the event loop. According to the documentation, asyncio primitives are not thread-safe, so invoking event.set() from another thread doesn't properly synchronize with the running event loop, which is why your program doesn't wake up when it should.
If you want to do anything asyncio-related, such as invoke Event.set, from outside the event loop thread, you need to use call_soon_threadsafe or equivalent. For example:
def receivedMsgCallback(self, msg):
if msg == ...:
self.loop.call_soon_threadsafe(self.event.set)
The event loop object should be made available to the CanBusManager object, perhaps by passing it to its constructor and assigning it to self.loop.
On a side note, if you are creating a task only to await it immediately, you don't need a task in the first place. In other words, you can replace task = asyncio.create_task(sequencer.doSequence()); await task with the simpler await sequencer.doSequence().

Emitting a list in a signal

I'm trying to create a version of QListWidget (in PySide) in which the itemClicked signal will carry not one item, but a list of all items in the QListWidget.
I tried different things, but no luck so far. This is what I have ATM:
class RelationsListWidget(QListWidget):
all_items = Signal(list)
item_list = []
def __init__(self):
QListWidget.__init__(self)
self.itemClicked.connect(self.gather_items)
def gather_items(self):
self.item_list = [self.item(i) for i in range(self.count())]
self.all_items.emit(self.item_list)
but when I connect it:
class NodeEditWindow(QDialog):
...
self.list_consumes = RelationsListWidget()
self.list_consumes.itemClicked.connect(self.create_SelectRelationsWindow)
...
#Slot(object)
def create_SelectRelationsWindow(self, list_widget):
print("create_SelectRelationsWindow: %s" % type(list_widget))
I'm getting:
create_SelectRelationsWindow: <class '__main__.NodeItem'>
so it carries only one item, not a list.
related questions:
How to connect custom signal to slot in pyside with the new syntax?
PyQt4.QtCore.pyqtSignal object has no attribute 'connect'

Python - Implementing stop method in threading.Thread

I'm using threads in my project I want to subclass threading.Thread and implement the stop method there, so I can subclass this class. look at this -
class MyThread(threading.Thread):
__metaclass__ = ABCMeta
def run(self):
try:
self.code()
except MyThread.__StopThreadException:
print "thread stopped."
#abstractmethod
def code(self):
pass
def stop(self):
tid = self.get_id()
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid,
ctypes.py_object(MyThread.__StopThreadException))
ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, 0)
class __StopThreadException(Exception):
"""The exception I raise to stop the sniffing"""
pass
def get_id(self):
for tid, tobj in threading._active.items():
if tobj is self:
return tid
class SniffThread(MyThread):
def code(self):
sniff(prn=self.prn)
def prn(self, pkt):
print "got packet"
t = SniffThread()
t.start()
time.sleep(10)
t.stop()
It doesn't work because the StopThreadException is raised in the main thread, and I need to find a way to raise it in the SniffThread. Do you have better way to implement the stop method? I need to do it because I work with blocking functions and I need a way to stop the Thread. I know I can use the lfilter of the sniff function and keep a flag but I don't want to do this. I don't want to do it because it will only work with scapy and I want to make an abstract class that can be inherited with a stop method that will work for any thread, the solution needs to be in the MyThread class.
Edit: I looked here and changed my code.

Resources