[PyQt] C++ destructors and PyQt?

Bryan A. Jones bjones at ece.msstate.edu
Tue Nov 4 16:30:55 GMT 2014


All,

I think I’ve answered my own question. The basic assumption:
parent.destroyed() will be emitted shortly before a class’ C++ destructor
would be invoked. For Python cleanup, simply connect a method to this slot
and perform cleanup. Does that seem reasonable? Also, does everyone agree
with comments in the code (using deleteLater() to destroy the Worker, then
setting it to None, for example)?

Thanks for any insight or input.

Bryan

# Test program to ask about destructors and PyQt4.

import sys, time
from PyQt4.QtGui import QApplication
from PyQt4.QtCore import QThread, QTimer, QObject, pyqtSignal

# A Worker that does nothing but wait as its computational task.
class Worker(QObject):
    def onStartWork(self):
        time.sleep(2)

class ThreadController(QObject):
    startWork = pyqtSignal()
    def __init__(self, parent):
        QObject.__init__(self, parent)
        # No parent object provided; if so, we get the error
        # "QObject::moveToThread: Cannot move objects with a parent".
        # Likewise, calling setParent after moving the object produces
        # "QObject::setParent: Cannot set parent, new parent is in a different
        # thread". So, we must manually invoke this object's destructor in
        # self.onParentDestroyed.
        self.worker = Worker()
        self.workerThread = QThread(self)
        self.worker.moveToThread(self.workerThread)

        # Since there's no way to invoke a method when the C++ destructor of
        # this class is called, approximate it by looking for the destroyed()
        # signal of the parent -- this object will soon have its destructor
        # invoked by the parent.
        parent.destroyed.connect(self.onParentDestroyed)
        self.startWork.connect(self.worker.onStartWork)
        self.workerThread.start()
        self.startWork.emit()

    # This is run shortly before this class's C++ destructor is invoked. It
    # emulates a C++ destructor by freeing resourced before the C++ class is
    # destroyed. Without this, we get nasty crashes since the Python portion
    # of the class is still alive. At a minimum, we see "QThread: Destroyed
    # while thread is still running" messages.
    def onParentDestroyed(self):
        print('onParentDestroyed')
        # Use deleteLater() to allow the worker to finish up any events in its
        # queue, then have its destructor invoked. Remove this class' reference
        # to it, allowing Python to garbage collect the Python half of the class
        # when the C++ part is deleted by deleteLater().
        self.worker.deleteLater()
        self.worker = None
        # Now, shut down the thread the Worker runs in.
        self.workerThread.quit()
        self.workerThread.wait()
        # Finally, detach (and probably garbage collect) the remaining object
        # used by this class.
        self.workerThread = None

if __name__ == '__main__':
    app = QApplication(sys.argv)

    # Exit the program shortly after the event loop starts up, but before the
    # thread finishes.
    QTimer.singleShot(500, app.exit)

    # Start a thread
    tm = ThreadController(app)

    # Run the main event loop.
    sys.exit(app.exec_())

​

On Mon, Nov 3, 2014 at 4:07 PM, Jones, Bryan <bjones at ece.msstate.edu> wrote:

>   All,
>
> Pardon the double post. I saw a few obvious bugs after hitting send. My
> apologies. Here is the improved version.
>
> Bryan
>
> # Test program to ask about destructors and PyQt4.
>
> import sys, time
> from PyQt4.QtGui import QApplication
> from PyQt4.QtCore import QThread, QTimer, QObject, pyqtSignal
>
> class Worker(QObject):
>     def onStartWork(self):
>         time.sleep(1)
>
> class ThreadController(QObject):
>     startWork = pyqtSignal()
>     def __init__(self, parent):
>         QObject.__init__(self, parent)
>         # No parent object provided; if so, we get the error
>         # "QObject::moveToThread: Cannot move objects with a parent".
>         # Likewise, calling setParent after moving the object produces
>         # "QObject::setParent: Cannot set parent, new parent is in a different
>         # thread". Who will deallocate this object, since it has no parent? I
>         # assume the answer is "Python, maybe". Is that good enough?
>         self.worker = Worker()
>         self.workerThread = QThread(self)
>         self.worker.moveToThread(self.workerThread)
>
>         # Use method #2 below. Commnet out for an ominous warning which will
>         # crash in a more complex program: "QThread: Destroyed while thread is
>         # still running".
>         #parent.aboutToQuit.connect(self.del_)
>         self.startWork.connect(self.worker.onStartWork)
>         self.workerThread.start()
>
>     # I'd like to run when when ~ThreadController is invoked. That's quite
>     # differentfrom when __del__ is invoked. How?
>     #
>     # 1. Give up. Just invoke it manually. However, this is error-prone (i.e.
>     #    I'll forget to do it at some point).
>     # 2. Connect app.aboutToQuit to this. This seems fairly reasonable, but
>     #    it would feel cleaner to invoke when the destructor is invoked,
>     #    rather than earlier. Also, this means that the QApplication would
>     #    need to be passed to every thread, which might involve awkwardness
>     #    in passing it through several intervening subclasses.
>     # 3. Connect destroyed(), which doesn't work -- this isn't emitted until
>     #    the object is mostly dead, far after ~ThreadController was invoked,
>     #    and won't be executed.
>     # 4. Have __del__ invoke this, which doesn't work -- it only happens after
>     #    ~ThreadController has finihed.
>     def del_(self):
>         print('del_')
>         self.workerThread.quit()
>         self.workerThread.wait()
>
>     def __del__(self):
>         print('__del__')
>         # Crash when uncommented.
>         #self.del_()
>
> if __name__ == '__main__':
>     app = QApplication(sys.argv)
>
>     # Exit the program shortly after the event loop starts up, but before the
>     # thread finishes.
>     QTimer.singleShot(200, app.exit)
>
>     # Start a thread
>     tm = ThreadController(app)
>
>     # Run the main event loop.
>     ret = app.exec_()
>     print('done')
>     sys.exit(ret)
>
>>
> On Mon, Nov 3, 2014 at 3:59 PM, Bryan A. Jones <bjones at ece.msstate.edu>
> wrote:
>
>>  All,
>>
>> I’m confused when it comes to the proper way to mesh Qt’s model of
>> creating a tree of parented objects which destroy themselves when no longer
>> used with Python’s “delete it when I feel like it” garbage collector. In
>> particular, I’d like to shut down threads when the underlying Qt objects
>> are destroyed, but I can’t figure out how to do that. Any advice and wisdom
>> would be appreciated. I've attached same code with my thoughts.
>>
>> Bryan
>>
>> # Test program to ask about destructors and PyQt4.
>>
>> import sys, time
>> from PyQt4.QtGui import QApplication
>> from PyQt4.QtCore import QThread, QTimer, QObject
>>
>> class Worker(QObject):
>>     def run(self):
>>         time.sleep(1)
>>
>> class ThreadController(QObject):
>>     def __init__(self, parent):
>>         QObject.__init__(self, parent)
>>         # No parent object provided; if so, we get the error
>>         # "QObject::moveToThread: Cannot move objects with a parent".
>>         # Who will deallocate this object, since it has no parent? I
>>         # assume the answer is "Python, maybe". Is that good enough?
>>         self.worker = Worker()
>>         self.workerThread = QThread(self)
>>         self.worker.moveToThread(self.workerThread)
>>         # Use method #2 below. Commnet out for a crash.
>>         parent.aboutToQuit.connect(self.del_)
>>         self.workerThread.start()
>>
>>     # I'd like to run when when ~ThreadController is invoked. That's quite
>>     # differentfrom when __del__ is invoked. How?
>>     #
>>     # 1. Give up. Just invoke it manually. However, this is error-prone (i.e.
>>     #    I'll forget to do it at some point).
>>     # 2. Connect app.aboutToQuit to this. This seems fairly reasonable, but
>>     #    it would feel cleaner to invoke when the destructor is invoked,
>>     #    rather than earlier. Also, this means that the QApplication would
>>     #    need to be passed to every thread, which might involve awkwardness
>>     #    in passing it through several intervening subclasses.
>>     # 3. Connect destroyed(), which doesn't work -- this isn't emitted until
>>     #    the object is mostly dead, far after ~ThreadController was invoked,
>>     #    and won't be executed.
>>     # 4. Have __del__ invoke this, which doesn't work -- it only happens after
>>     #    ~ThreadController has finihed.
>>     def del_(self):
>>         print('del_')
>>         self.workerThread.quit()
>>         self.workerThread.wait()
>>
>>     def __del__(self):
>>         print('__del__')
>>         # Crash when uncommented.
>>         #self.del_()
>>
>> if __name__ == '__main__':
>>     app = QApplication(sys.argv)
>>
>>     # Exit the program shortly after the event loop starts up, but before the
>>     # thread finishes.
>>     QTimer.singleShot(200, app.exit)
>>
>>     # Start a thread
>>     tm = ThreadController(app)
>>
>>     # Run the main event loop.
>>     ret = app.exec_()
>>     print('done')
>>     sys.exit(ret)
>>
>>>>  --
>> Bryan A. Jones, Ph.D.
>> Associate Professor
>> Department of Electrical and Computer Engineering
>> 231 Simrall / PO Box 9571
>> Mississippi State University
>> Mississippi state, MS 39762
>> http://www.ece.msstate.edu/~bjones
>> bjones AT ece DOT msstate DOT edu
>> voice 662-325-3149
>> fax 662-325-2298
>>
>> Our Master, Jesus Christ, is on his way. He'll show up right on
>> time, his arrival guaranteed by the Blessed and Undisputed Ruler,
>> High King, High God.
>> - 1 Tim. 6:14b-15 (The Message)
>>
>
>
>
>  --
> Bryan A. Jones, Ph.D.
> Associate Professor
> Department of Electrical and Computer Engineering
> 231 Simrall / PO Box 9571
> Mississippi State University
> Mississippi state, MS 39762
> http://www.ece.msstate.edu/~bjones
> bjones AT ece DOT msstate DOT edu
> voice 662-325-3149
> fax 662-325-2298
>
> Our Master, Jesus Christ, is on his way. He'll show up right on
> time, his arrival guaranteed by the Blessed and Undisputed Ruler,
> High King, High God.
> - 1 Tim. 6:14b-15 (The Message)
>



-- 
Bryan A. Jones, Ph.D.
Associate Professor
Department of Electrical and Computer Engineering
231 Simrall / PO Box 9571
Mississippi State University
Mississippi state, MS 39762
http://www.ece.msstate.edu/~bjones
bjones AT ece DOT msstate DOT edu
voice 662-325-3149
fax 662-325-2298

Our Master, Jesus Christ, is on his way. He'll show up right on
time, his arrival guaranteed by the Blessed and Undisputed Ruler,
High King, High God.
- 1 Tim. 6:14b-15 (The Message)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.riverbankcomputing.com/pipermail/pyqt/attachments/20141104/99d6f668/attachment-0001.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: dest.py
Type: application/octet-stream
Size: 2751 bytes
Desc: not available
URL: <http://www.riverbankcomputing.com/pipermail/pyqt/attachments/20141104/99d6f668/attachment-0001.obj>


More information about the PyQt mailing list