【问题标题】:Lifetime of a python object passed with a Qt signal to another thread使用 Qt 信号传递给另一个线程的 python 对象的生命周期
【发布时间】:2022-07-07 06:36:08
【问题描述】:

我有一个事件生产者线程,在该Producer线程中创建事件实例并将它们作为信号参数传递给GUI线程,在将对象移动到GUI线程之后(在现实生活中的代码中,所以对象可以添加到模型中进行显示)。

在这里描述的第一次尝试中:

  • PyQt5 成功让对象通过,但它在槽中被视为 QObject 而不是事件
  • PySide2 只是段错误
#! /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_())

两者都在 PyQt5 下显示相同的打字问题,被我的 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

...在 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
...

事实证明,正如https://stackoverflow.com/a/12363609/6285023 所暗示的那样,Event 对象在超出范围时被销毁——例如。只需在 Producer 对象中保留对它的引用即可避免此问题。

但这很浪费(在一个线程中保留大量事件的列表,当它们已经在另一个线程的数据模型中被引用时),并且感觉不是很pythonic:另一个线程得到了看起来像 ref 的东西到对象——至少它感觉像一个标准的 ref,除了它似乎没有包含在通常的 refcounting 中,而且被删除得太早了。

这看起来像是一个 C++ism 没有正确翻译成 Python,并且可以被视为 PyQt/PySide 错误,对吧?

是否有更合适的方法来实现相同的结果,还是我们必须接受上述解决方法?

【问题讨论】:

  • 为什么Event会继承QObject?如果它只是简单地继承object,那么一切都会按预期工作(包括保留类型)。
  • 好点,确实效果很好,并且完全避免了这个问题(@eyllanesc 准确地描述了)。我使用 QObject 能够将对象传递给 QML,但我确实不需要它,并且仅使用 objects 可以提供明显更好的性能。

标签: python pyqt5 pyside2 qt-signals


【解决方案1】:

C++和python的内存管理有区别,前者可以使用动态内存(一般是指针),后者使用引用。因此,每个库(PyQt 和 PySide)都决定了它想要赋予它的行为,因为 Qt 没有发出任何关于它的信号。如果对象在通过信号发送时没有 QObject 作为其父对象,那么 PyQt 似乎具有对 python 对象的引用,而 PySide 则没有。因此,也许您可​​以向 PySide 询问该功能以进行讨论,如果更改被接受,那么在未来的版本中可以将其推送到 PySide6(因为它也具有相同的行为)。

另一方面,在这种情况下,一种解决方法是让 QObject 有一个父对象,在这种情况下,它可以是一个位于它被移动到的同一线程上的对象,然后将其删除:

class Producer(QtCore.QThread):
    woo = QtSignal(Event)

    def run(self):
        print_thread("Producer.run")
        evt = Event("bar")
        qapp_instance = QtCore.QCoreApplication.instance()
        evt.moveToThread(qapp_instance.thread())
        evt.setParent(qapp_instance)
        print("emitting", evt)
        self.woo.emit(evt)


class Listener(QObject):
    def handle_event(self, event):
        print_thread("Listener.handle_event")
        print("got", event)
        event.deleteLater()
        exit(0)

【讨论】:

    【解决方案2】:

    我的问题的答案正如@ekhumoro 在他对我的问题的评论中所描述的那样:我的Event 类不需要继承QObject

    class Event():
        def __init__(self, name: str):
            self.name = name
    

    OTOH @eyllanesc 对问题发生原因的描述非常准确,并且确实允许解决更普遍的问题,如果需要将 QObject 传递到其 C++ 范围之外。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-03-16
      • 1970-01-01
      • 1970-01-01
      • 2011-04-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-19
      相关资源
      最近更新 更多