PyQt4 Wait in thread for user input from GUI - python

I have a thread class "MyThread" and my main application which is simply called "Gui". I want to create a few objects from the thread class but for this example I created only one object. The thread class does some work, then emits a signal to the Gui class, indicating that a user input is needed (this indication for now is simply changing the text of a button). Then the thread should wait for a user input (in this case a button click) and then continue doing what it is doing...
from PyQt4 import QtGui, QtCore
class MyTrhead(QtCore.QThread):
trigger = QtCore.pyqtSignal(str)
def run(self):
print(self.currentThreadId())
for i in range(0,10):
print("working ")
self.trigger.emit("3 + {} = ?".format(i))
#### WAIT FOR RESULT
time.sleep(1)
class Gui(QMainWindow, Ui_MainWindow):
def __init__(self, parent=None):
super(Gui, self).__init__(parent)
self.setupUi(self)
self.pushButton.clicked.connect(self.btn)
self.t1 = MyTrhead()
self.t1.trigger.connect(self.dispaly_message)
self.t1.start()
print("thread: {}".format(self.t1.isRunning()))
#QtCore.pyqtSlot(str)
def dispaly_message(self, mystr):
self.pushButton.setText(mystr)
def btn(self):
print("Return result to corresponding thread")
if "__main__" == __name__:
import sys
app = QtGui.QApplication(sys.argv)
m = Gui()
m.show()
sys.exit(app.exec_())
How can I wait in (multiple) threads for a user input?

By default, a QThread has an event loop that can process signals and slots. In your current implementation, you have unfortunately removed this behaviour by overriding QThread.run. If you restore it, you can get the behaviour you desire.
So if you can't override QThread.run(), how do you do threading in Qt? An alternative approach to threading is to put your code in a subclass of QObject and move that object to a standard QThread instance. You can then connect signals and slots together between the main thread and the QThread to communicate in both directions. This will allow you to implement your desired behaviour.
In the example below, I've started a worker thread which prints to the terminal, waits 2 seconds, prints again and then waits for user input. When the button is clicked, a second separate function in the worker thread runs, and prints to the terminal in the same pattern as the first time. Please note the order in which I use moveToThread() and connect the signals (as per this).
Code:
from PyQt4.QtCore import *
from PyQt4.QtGui import *
import time
class MyWorker(QObject):
wait_for_input = pyqtSignal()
done = pyqtSignal()
#pyqtSlot()
def firstWork(self):
print 'doing first work'
time.sleep(2)
print 'first work done'
self.wait_for_input.emit()
#pyqtSlot()
def secondWork(self):
print 'doing second work'
time.sleep(2)
print 'second work done'
self.done.emit()
class Window(QWidget):
def __init__(self, parent = None):
super(Window, self).__init__()
self.initUi()
self.setupThread()
def initUi(self):
layout = QVBoxLayout()
self.button = QPushButton('User input')
self.button.setEnabled(False)
layout.addWidget(self.button)
self.setLayout(layout)
self.show()
#pyqtSlot()
def enableButton(self):
self.button.setEnabled(True)
#pyqtSlot()
def done(self):
self.button.setEnabled(False)
def setupThread(self):
self.thread = QThread()
self.worker = MyWorker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.firstWork)
self.button.clicked.connect(self.worker.secondWork)
self.worker.wait_for_input.connect(self.enableButton)
self.worker.done.connect(self.done)
# Start thread
self.thread.start()
if __name__ == "__main__":
app = QApplication([])
w = Window()
app.exec_()

Related

PyQt5 Qtimer don't start

i'm stucked on a Qtimer that don't start, so the number that i need to update on the GUI is never showed.
i don't want to use a while loop inside the code because i need to change some values in real time without any problem.
i really don't understand why it don't start... or better, it start, but don't run the function update_label.
any suggestion?
Thanks a lot for your time!
here is my code:
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
super(MainWindow, self).__init__(*args, **kwargs)
self.setObjectName("MainWindow")
self.resize(1300, 768)
self.setMinimumSize(1300,768)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("Bot", "Bot"))
self.number = QtWidgets.QLabel(self)
self.number .setGeometry(QtCore.QRect(1100, 10, 300, 20))
self.number .setObjectName("number")
self.show()
self.threadpool = QThreadPool()
self.updater = Updater()
self.threadpool.start(self.updater)
self.updater.update_progress.latest_number.connect(self.update_number)
#pyqtSlot(str)
def update_number(self, val):
x = str(val)
self.number.setText("Current block: " + x)
class Updater(QRunnable):
def __init__(self):
super(Updater, self).__init__()
self.update_progress = WorkerSignal()
print("started")
def run(self):
self.timer = QTimer()
self.timer.setInterval(2000)
self.timer.timeout.connect(self.update_label)
self.timer.start()
def update_label(self):
provider = configfile.provider
number = str(nmbr.latest_numer(provider))
self.update_progress.latest_number.emit(number)
print("update label started")
After tyring to understand your script, I reached the conclusion that QRunnable is automatically released (or stopped) once Updater.run(self) reaches its end.
But this is supposed to happen, because that's why you should use a QThreadPool in the first place. It is supposed to recycle expired threads in the background of the application.
So, as you're running a QTimer in the thread, QThreadPool thinks that Updater thread is dead, while in reality, the timer is still running on the background of a background thread. So it does its job and releases Updater from the Thread pool.
What you must do in order to keep both the Timer and Updater alive is to manager the Updater thread's lifecycle yourself.
So instead of using QThreadPool and QRunnable, you must go one level lower in terms of abstraction, and use the QThread and QObject themselves in order to control when the thread is going to be stopped.
from PySide2 import QtWidgets, QtCore
from PySide2.QtWidgets import QApplication, QMainWindow, QWidget, QVBoxLayout
from PySide2.QtCore import Qt, QThreadPool, QRunnable, Slot, Signal, QObject, QTimer, QThread
class WorkerSignal(QObject):
# Changed pyqtSignal to Signal
latest_number = Signal(str)
class Worker(QObject):
def __init__(self):
QObject.__init__(self)
self.update_progress = WorkerSignal()
def update_label(self):
print("update label started")
try:
provider = configfile.provider
number = str(nmbr.latest_numer(provider))
self.update_progress.latest_number.emit(number)
except:
print("Error, but just for testing...")
self.update_progress.latest_number.emit("0")
def main(self):
print('Init worker')
self.timer = QTimer()
self.timer.setInterval(2000)
self.timer.timeout.connect(self.update_label)
self.thread().finished.connect(self.timer.stop)
self.timer.start()
class Updater(QThread):
def __init__(self):
QThread.__init__(self)
self.worker = Worker()
# Move worker to another thread, to execute in parallel
self.worker.moveToThread(self)
# Set which method from Worker that should excute on the other thread.
# In this case: Worker.main
self.started.connect(self.worker.main)
class MainWindow(QMainWindow):
def __init__(self, *args, **kwargs):
QMainWindow.__init__(self, *args, **kwargs)
self.setObjectName("MainWindow")
self.resize(1300, 768)
self.setMinimumSize(1300,768)
_translate = QtCore.QCoreApplication.translate
self.setWindowTitle(_translate("SniperBot", "SniperBot"))
self.number = QtWidgets.QLabel(self)
self.number.setGeometry(QtCore.QRect(1100, 10, 300, 20))
self.number.setObjectName("number")
# I did this to see the label, as it was not attached to any
# widget's layout on your script.
widget = QWidget()
layout = QVBoxLayout()
widget.setLayout(layout)
layout.addWidget(self.number)
self.setCentralWidget(widget)
self.scene = widget
self.show()
# Create the Updater thread.
self.updater = Updater()
# Connect the Worker's signal to self.update_number
self.updater.worker.update_progress.latest_number.connect(self.update_number)
# Start the thread
self.updater.start()
def closeEvent(self, evt):
# Before exiting from the application, remember, we control now
# the Updater Thread's lifecycle. So it's our job to stop the thread
# and wait for it to finish properly.
self.updater.quit()
self.updater.wait()
# Accept the event. Exit the application.
evt.accept()
# Changed pyqtSlot to Slot
#Slot(str)
def update_number(self, val):
x = str(val)
print("UPDATE NUMBER: ", x)
self.number.setText("Current block: " + x)
if __name__ == '__main__':
app = QApplication()
win = MainWindow()
win.show()
app.exec_()
What you can do now is follow this pattern and start implementing from here. There are many tutorials that use the moveToThread method. Just be careful to not let the thread or any object be recycled by the python's garbage collector, in order to avoid many other problems.
The script is working, however I use PySide2 instead of PyQt5. So some variable and modules names may change, so just rename them when you try to run the script on your end.
There were a few errors on Worker.update_label(self), which is just a copied script from the old Updater.update_label(self). I don't know what are the variables: configfile or nmbr as they are not initialized on your post. So I made a try-except block to handle the error and make the script work just for testing.

QThread stops event processing

I would like to start a Thread with PyQt, that performs some actions independently of the rest of the app. This includes running an external program. This may take some minutes.
My problem is that using QThread for this stops the whole app.
When running this small program, clicking the button will freeze the app for 10 seconds.
What can I do to make QThread behave as a Thread. I know that I can add some timers and divide it into events, but that is not my idea of a thread.
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
from PyQt5.QtCore import QThread
class ProgramThread(QThread):
def __init__(self):
QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
QThread.sleep(10)
class App(QWidget):
def __init__(self):
super().__init__()
thread = QPushButton('Start Thread',self)
thread.clicked.connect(self.startthread)
thread.move(20,100)
self.show()
def startthread(self):
t = ProgramThread()
t.start()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = App()
sys.exit(app.exec_())
QThread is not a thread, it is a thread handler. In your case the problem is that the variable t is local so it will be deleted after executing the start, at that moment the __del__ method is called, and this calls wait() that runs in the main thread blocking the GUI, this blocking It will be until the run method finishes executing, so in conclusion the problem is that t is a local variable, the solution is to keep it in time so there are 2 possibilities: 1) pass it to self as a parent or 2) make it a member of the class:
class ProgramThread(QThread):
def run(self):
QThread.sleep(10)
def __del__(self):
self.wait()
class App(QWidget):
def __init__(self):
super().__init__()
thread = QPushButton('Start Thread',self)
thread.clicked.connect(self.startthread)
thread.move(20,100)
self.show()
def startthread(self):
# method 1
t = ProgramThread(self)
t.start()
# method 2
# self.t = ProgramThread()
# self.t.start()

Is it possible to use a ThreadPoolExecutor with a QThread? Seems to create dummy thread that doesn't terminate

I'm making an application in PyQt5 that runs a variety of long running tasks, such as scraping web pages. In order to avoid crashing the GUI, I've been using QThreads and QObjects
Currently I have a class that inherits QObject and contains a method for scraping the web pages. I move that object on to a QThread, connect a finished signal with a method that quits the thread and then start the thread.
import sys
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import threading
from concurrent.futures import ThreadPoolExecutor
import requests
class Main(QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__(parent)
self.init_ui()
self.test_obj = None
self.thread = QThread(self)
def init_ui(self):
widget = QWidget()
layout = QHBoxLayout()
widget.setLayout(layout)
thread_button = QPushButton("Start Thread")
check_button = QPushButton("Check Thread")
layout.addWidget(thread_button)
layout.addWidget(check_button)
thread_button.clicked.connect(self.work)
check_button.clicked.connect(self.check)
self.setCentralWidget(widget)
self.show()
def work(self):
self.test_obj = TestObject()
self.test_obj.moveToThread(self.thread)
self.test_obj.finished.connect(self.finished)
self.thread.started.connect(self.test_obj.test)
self.thread.start()
def check(self):
for t in threading.enumerate():
print(t.name)
#pyqtSlot()
def finished(self):
self.thread.quit()
self.thread.wait()
print("Finished")
class TestObject(QObject):
finished = pyqtSignal()
def __init__(self):
super(TestObject, self).__init__()
def test(self):
with ThreadPoolExecutor() as executor:
executor.submit(self.get_url)
self.finished.emit()
def get_url(self):
res = requests.get("http://www.google.com/")
print(res)
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = Main()
sys.exit(app.exec_())
Everything works as expected, I get a:
"Response [200]"
"Finished"
Printed in the console. However when I check the running threads, it shows dummy-1 thread is still running. Every time I run, it creates an additional dummy thread. Eventually I am unable to create any more threads and my application crashes.
Is it possible to use a ThreadPoolExecutor on a QThread like this? If so, is there a correct way to ensure that I don't have these dummy threads still running once I've finished my task?

Python crashes with 2 worker threads in PyQt5 application

I would like to have 2 worker threads in my application. One should start running as soon as the GUI is loaded and another one should start later by some signal. Let's say it's a button click.
I came across a weird behavior when my Python interpreter crashes(as in shows me Windows error "Python stopped working", no stack trace) on executing the second thread.
Here is an example, that will crash right after I click the button.
class Worker(QtCore.QThread):
def __init__(self, method_to_run):
super().__init__()
self.method = method_to_run
def run(self):
self.method()
class Window(QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QPushButton('Test', self)
self.label = QLabel(self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.worker = Worker(self.test_method)
self.worker.start()
def handleButton(self):
self.label.setText('Button Clicked!')
worker = Worker(self.test_method)
worker.start()
#staticmethod
def test_method():
res = [i*i for i in range(100500)]
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
What's more weird is that it doesn't crash when you debug the application for some reason.
What am I missing here?
Edit
I could get much from the crashdump, as I don't have symbols for QT. But it looks like the crash happens inside QtCore.dll
ExceptionAddress: 00000000632d4669 (Qt5Core!QThread::start+0x0000000000000229)
The problem is that you didn't save a reference to the thread so it was deleted as soon as you exited handleButton. If you save a reference, that begs the question of how to handle its lifespan.
QThread is more than just a wrapper to a system thread - it implements other services that let you wire a thread into your GUI. You can use its finished handler to signal the widget when it terminates to do any cleanup.
In this example, I save the worker as self.worker2 and block starting the worker a second time until the first one completes.
import PyQt5
import PyQt5.QtCore as QtCore
from PyQt5.QtWidgets import *
import time
class Worker(QtCore.QThread):
def __init__(self, method_to_run):
super(Worker, self).__init__()
self.method = method_to_run
def run(self):
self.method()
class Window(QWidget):
def __init__(self):
super().__init__()
self.button = QPushButton('Test', self)
self.label = QLabel(self)
self.button.clicked.connect(self.handleButton)
layout = QVBoxLayout(self)
layout.addWidget(self.label)
layout.addWidget(self.button)
self.worker = Worker(self.test_method)
self.worker.start()
self.worker2 = None
def handleButton(self):
self.label.setText('Button Clicked!')
# likely better to disable the button instead... but
# this shows events in action.
if self.worker2:
self.label.setText('Worker already running')
else:
self.worker2 = Worker(self.test_method)
self.worker2.finished.connect(self.handle_worker2_done)
self.worker2.start()
def handle_worker2_done(self):
self.worker2 = None
self.label.setText('Worker done')
#staticmethod
def test_method():
#res = [i*i for i in range(100500)]
time.sleep(3)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
Your click triggered thread isn't actually trying to do work in a thread. You called the method, rather than passing the method as an argument, so you're trying to use the return value of test_method (None) as the method to run.
Change:
worker = Worker(self.test_method()) # Calls test_method and tries to run None
to:
worker = Worker(self.test_method) # Don't call test_method

GUI freezing with PySide and multiprocessing

I am trying to offload a heavy background job to a multiprocessing process. I just want the separate process to be able to report it's progress to my GUI. Here's my last try, the GUI is simple, a couple of buttons and a progress bar:
from PySide.QtGui import *
from PySide.QtCore import *
import sys
from multiprocessing import Process, Pipe
import time
class WorkerClass:
#This class has the job to run
def worker(self, pipe):
for i in range(101):
pipe.send(i)
time.sleep(.02)
class WorkStarter(QThread):
#this thread takes a widget and updates it using progress sent from
#process via Pipe
def __init__(self, progressBar):
super().__init__()
self.progress_bar = progressBar
def run(self):
worker_obj = WorkerClass()
myend, worker_end = Pipe(False)
self.p = Process(target=worker_obj.worker, args=(worker_end,))
self.p.start()
while True:
val = myend.recv()
self.progress_bar.setValue(val)
if val == 100:
break
class WorkingWidget(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle('Blue collar widget')
layout = QHBoxLayout()
start_btn = QPushButton('Start working')
start_btn.clicked.connect(self.startWorking)
end_btn = QPushButton('End working')
end_btn.clicked.connect(self.endWorking)
layout.addWidget(start_btn)
layout.addWidget(end_btn)
self.progress_bar = QProgressBar()
layout.addWidget(self.progress_bar)
self.setLayout(layout)
def startWorking(self):
self.thread = WorkStarter(self.progress_bar)
self.thread.start()
def endWorking(self):
self.thread.terminate()
if __name__ == '__main__':
app = QApplication(sys.argv)
main = WorkingWidget()
main.show()
sys.exit(app.exec_())
I cannot pass any QObject as an argument to the process, since that is not pickleable:
#cannot do the following
...
def startWorking(self):
self.worker_obj = WorkerClass()
#pass the progress bar to the process and the process updates the bar
self.p = Process(target=self.worker_obj.worker, args=(self.progress_bar,))
The problem is that this gui some times works, other times it freezes (So please press 'start' multiple times until it freezes :) ), and here on Windows it says : pythonw.exe has stopped working...
Any clue what's the reason for that?. I cannot figure it out by myself. Thanks
You are not supposed to create the object inside "run" method of QThread, emit signal from "run", implement a function say "callerFunction" create object in this function and finally call this function on signal which is emitted by the "run" function.
You can emit the signal in the while loop that you have already created.
Have a look at this solution
don't create a python process, QThread is sufficient for this job

Categories