Issue
I'm trying to run a long task in a separate thread in PySide so that the main thread can keep processing GUI events. I have read the proper way to do this is:
- Encapsulate the task in a
QObject
subclass where the work is done in arun()
method and emits afinished
signal when done. - Create a new
QThread
and set the task object's thread affinity to it usingQObject.moveToThread()
. - Connect the thread's
start
signal to the task'srun()
method. - Start the thread using
QThread.start()
.
However, I'm running into a strange problem where if the run()
method is wrapped in a slot, the function will be run in the main thread instead of the thread the object belongs to. If I leave it as a standard Python method, everything works fine.
Here is a minimal example I created:
#!/usr/bin/env python
import sys
from PySide import QtCore, QtGui
class Task(QtCore.QObject):
"""Does some work and emits a signal when done."""
finished = QtCore.Signal(object)
def run1(self):
"""Runs task and emits finished() signal when done."""
try:
# Try running the task
result = self._run()
except:
self.finished.emit(None)
else:
self.finished.emit(result)
@QtCore.Slot()
def run2(self):
"""Same as run1, but wrapped in a slot."""
self.run1()
def _run(self):
"""Override in subclass"""
pass
class TestTask(Task):
"""Prints thread ID."""
def __init__(self, name):
super().__init__()
self.name = name
def _run(self):
print('{} thread ID:'.format(self.name), QtCore.QThread.currentThreadId())
return 'success'
def main():
gui = QtGui.QApplication([])
print('Main thread ID: ', QtCore.QThread.currentThreadId())
# thread1 calls task1.run1()
task1 = TestTask('task1')
task1.finished.connect(lambda r: print('Task 1 finished:', r))
thread1 = QtCore.QThread()
task1.moveToThread(thread1)
thread1.started.connect(task1.run1)
# thread2 calls task2.run2()
task2 = TestTask('task2')
task1.finished.connect(lambda r: print('Task 2 finished:', r))
thread2 = QtCore.QThread()
task2.moveToThread(thread2)
thread2.started.connect(task2.run2)
# Start both threads
thread1.start()
thread2.start()
# Run event loop (doesn't actually return)
sys.exit(gui.exec_())
if __name__ == '__main__':
main()
This produces the following output:
Main thread ID: 139962303178496
task1 thread ID: 139961642776320
task2 thread ID: 139962303178496
Task 2 finished success
Task 1 finished success
It isn't a huge deal to leave run()
as a standard Python method, but I'm wondering why this happens. This is with QT4.8 and PySide 1.2.4.
Solution
This may be caused by a possible bug in PySide. The issue seems to be caused by inheriting a base-class that has a decorated slot. If this slot is moved into the subclass, the problem goes away:
class TestTask(Task):
...
@QtCore.Slot()
def run2(self):
"""Same as run1, but wrapped in a slot."""
self.run1()
(PS: as a nother data point, it's worth noting that your original example works fine in PyQt4).
UPDATE:
As suspected, this is caused by a known bug in PySide: see PYSIDE-249.
Answered By - ekhumoro
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.