I have an event-producer thread, creating Event instances in that Producer
thread and passing them to the GUI thread as signal argument, after moving the object to the GUI thread (in real-life code, so the object can be added into a Model for displaying).
In a first attempts described here:
QObject
not an Event#! /usr/bin/env python3
import sys, threading
if True:
from PySide2 import QtCore
from PySide2.QtCore import QObject
QtSignal = QtCore.Signal
else:
from PyQt5 import QtCore
from PyQt5.QtCore import QObject
QtSignal = QtCore.pyqtSignal
def print_thread(ctx):
print(ctx, "in", threading.current_thread().name)
class Event(QObject):
def __init__(self, name: str):
super().__init__()
self.name = name
class Producer(QtCore.QThread):
woo = QtSignal(Event)
def run(self):
print_thread("Producer.run")
evt = Event("bar")
evt.moveToThread(QtCore.QCoreApplication.instance().thread())
print("emitting", evt)
self.woo.emit(evt)
class Listener(QObject):
def handle_event(self, event):
print_thread("Listener.handle_event")
print("got", event)
assert type(event) is Event
exit(0)
app = QtCore.QCoreApplication(sys.argv)
l = Listener()
p = Producer()
p.woo.connect(l.handle_event)
p.start()
sys.exit(app.exec_())
Both show the same typing issue under PyQt5, caught by my assert
:
Producer.run in Dummy-1
emitting <__main__.Event object at 0x7af7ad95b790>
Listener.handle_event in MainThread
got <PyQt5.QtCore.QObject object at 0x7af7ad95b790>
Traceback (most recent call last):
File "/home/user/soft/bt2viz/testsig.py", line 35, in handle_event
assert type(event) is Event
AssertionError
Aborted
... and under PySide2:
$ gdb --args python3 testsig.py
GNU gdb (Debian 10.1-1.7) 10.1.90.20210103-git
...
(gdb) r
Starting program: /usr/bin/python3 testsig.py
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0x7ffff3b4f700 (LWP 6190)]
Producer.run in Dummy-1
emitting <__main__.Event(0x7fffec005740) at 0x7ffff75577c0>
[Thread 0x7ffff3b4f700 (LWP 6190) exited]
Thread 1 "python3" received signal SIGSEGV, Segmentation fault.
0x00007fffec005760 in ?? ()
(gdb) bt
#0 0x00007fffec005760 in ?? ()
#1 0x00007ffff6add361 in QObject::property (this=this@entry=0x7fffec005740, name=name@entry=0x7ffff6d6e5f0 <PySide::invalidatePropertyName> "_PySideInvalidatePtr")
at kernel/qobject.cpp:4086
#2 0x00007ffff6d6ae17 in PySide::getWrapperForQObject (cppSelf=0x7fffec005740, sbk_type=0xbbb910) at ./sources/pyside2/libpyside/pyside.cpp:441
...
As it turns out, and as hinted by https://stackoverflow.com/a/12363609/6285023, what happens is that the Event object gets destroyed when getting out of scope -- and that eg. just keeping a ref to it within the Producer object avoids the issue.
But that is wasteful (keeping a list of a large number of events in one thread, when they are already referenced in the other thread's data model), and does not feel very pythonic: the other thread gets what looks like a ref to the object -- at least it feels like a standard ref, except that it does not seem to be included in the usual refcounting, and gets deleted too early.
This looks like a C++ism not properly translating into Python, and could be considered as a PyQt/PySide bug, right ?
Is there a more proper way to achieve the same result, or do we have to live with the above workaround ?
The answer to my problem is as described by @ekhumoro in his comment to my question: my Event
class does not need to inherit QObject
:
class Event():
def __init__(self, name: str):
self.name = name
OTOH @eyllanesc's description of why the problem happens is really accurate, and does allow to solve the more general problem, should the need to pass a QObject out of its C++ scope.