I recently wanted to try Wing IDE instead of Eclipse+Pydev for programming simple roguelike game using PyQt5. This game uses separate QThread with QObject inside it to process game state without freezing the GUI. However, my current application, which works well with standard and Eclipse's interpreter, freezes in Wing IDE. Here I post simple code which represents the problem:
import sys, time
from PyQt5.QtCore import pyqtSignal, pyqtSlot, Qt, QObject, QThread
from PyQt5.QtWidgets import QApplication, QMainWindow, QPushButton, QLCDNumber, QVBoxLayout, QWidget
class Counter(QObject):
'''
QObject-based class which works inside separate thread and
emits numbers from 1 to 10 to the GUI thread and then stops.
'''
new_value = pyqtSignal(int)
ended = pyqtSignal()
def __init__(self):
QObject.__init__(self)
self.isStopped = False
@pyqtSlot()
def start(self):
self.isStopped = False
for n in range(1, 11):
if not self.isStopped:
self.new_value.emit(n)
time.sleep(0.3)
else:
break
self.ended.emit()
@pyqtSlot()
def stop(self):
self.isStopped = True
class SimpleWindow(QMainWindow):
'''
Application window with 3 buttons and 1 LCD display.
'''
def __init__(self):
QMainWindow.__init__(self)
# Adding and configuring widgets
self.central = QWidget(self)
self.central.resize(100, 150)
self.resize(100, 150)
self.layout = QVBoxLayout()
self.central.setLayout(self.layout)
self.start_QBtn = QPushButton()
self.start_QBtn.setText('Start')
self.stop_QBtn = QPushButton()
self.stop_QBtn.setText('Stop')
self.number_LCD = QLCDNumber()
self.status_QBtn = QPushButton()
self.status_QBtn.setText('Status')
self.layout.addWidget(self.start_QBtn)
self.layout.addWidget(self.stop_QBtn)
self.layout.addWidget(self.status_QBtn)
self.layout.addWidget(self.number_LCD)
# Creating new thread and adding QObject-based object to it
self.thread = QThread()
self.counter = Counter()
self.counter.moveToThread(self.thread)
# Connecting button signals to slots
self.start_QBtn.clicked.connect(self.thread.start)
self.status_QBtn.clicked.connect(self.status)
self.stop_QBtn.clicked.connect(lambda: self.counter.stop())
# Connecting thread signals to slots
self.counter.new_value.connect(self.show_value)
self.counter.ended.connect(self.thread.quit)
self.thread.started.connect(self.counter.start)
self.thread.start()
@pyqtSlot(int)
def show_value(self, number):
'''
Display value obtained from Counter() in the LCD widget.
'''
self.number_LCD.display(number)
@pyqtSlot()
def status(self):
'''
Print thread status in the console.
'''
print('Thread is running: ', self.thread.isRunning())
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SimpleWindow()
window.show()
sys.exit(app.exec_())
Application starts, displays numbers from 1 to 10 as intended and then freezes. It also freezes when trying to push Stop button. Is there a solution to keep using QThread with Wing IDE?
My system is Windows 8 (x64), Python 3.7.1, Wing IDE Personal 6.1; PyQt5.11 (also checked with PyQt 5.7); Qt 5.7.
I can replicate this and it looks like moveToThread() is not being supported correctly. Here's another test case that has three tests in it, one that subclasses QThread, another that uses moveToThread(), and a third that subclasses QRunnable. The first and last work for me and the one using moveToThread() also does not work:
# From http://stackoverflow.com/questions/6783194/background-thread-with-qthread-in-pyqt
# with minor modifications
import time
import sys
from PyQt5 import QtCore
# Hack needed to avoid _NotifyModule bug
from PyQt5.QtCore import *
# Subclassing QThread
# http://qt-project.org/doc/latest/qthread.html
class AThread(QtCore.QThread):
def run(self):
count = 0
while count < 5:
time.sleep(1)
print("Increasing")
count += 1
# Subclassing QObject and using moveToThread
# http://blog.qt.digia.com/blog/2007/07/05/qthreads-no-longer-abstract
class SomeObject(QtCore.QObject):
finished = QtCore.pyqtSignal()
def longRunning(self):
count = 0
while count < 5:
time.sleep(1)
print("Increasing")
count += 1
self.finished.emit()
# Using a QRunnable
# http://qt-project.org/doc/latest/qthreadpool.html
# Note that a QRunnable isn't a subclass of QObject and therefore does
# not provide signals and slots.
class Runnable(QtCore.QRunnable):
def run(self):
count = 0
app = QtCore.QCoreApplication.instance()
while count < 5:
print("Increasing")
time.sleep(1)
count += 1
app.quit()
def usingQThread():
app = QtCore.QCoreApplication([])
thread = AThread()
thread.finished.connect(app.exit)
thread.start()
sys.exit(app.exec_())
def usingMoveToThread():
app = QtCore.QCoreApplication([])
objThread = QtCore.QThread()
obj = SomeObject()
obj.moveToThread(objThread)
obj.finished.connect(objThread.quit)
objThread.started.connect(obj.longRunning)
objThread.finished.connect(app.exit)
objThread.start()
sys.exit(app.exec_())
def usingQRunnable():
app = QtCore.QCoreApplication([])
runnable = Runnable()
QtCore.QThreadPool.globalInstance().start(runnable)
sys.exit(app.exec_())
if __name__ == "__main__":
usingQThread()
#usingMoveToThread()
#usingQRunnable()
Note that you have to uncomment the one to test, since they all call sys.exit so only one can be tried at a time.
I'll see if we can fix this in a future release of Wing. Thanks for posting this!