threading queue blocks main pyqt5 GUI window - python

Although I read a ton of posts on the web, but couldn't find a solution for my self.
Basically, I am using pyqt5 for making a GUI application and am using threading for creating threads and lastly using queue I tried to send signals between main and child thread.
I have a qt5 progress bar in which it is value needs to updated by the child thread progress. When I try to use progressbar.setValue(x) I get below error:
QObject::setParent: Cannot set parent, new parent is in a different thread
The reason for above is that I cannot update the progress bar value from child thread, it needs to happen from main thread.
So I tried to use queue and send a message to the main thread to do it.
I tried below:
def buttonclicked():
global progressQueue
progressQueue = Queue()
thread = threading.Thread(target=childfunc, daemon=True, name="nemo", args=())
thread.start()
progressQueue.join()
while True:
if not progressQueue.empty():
msg = progressQueue.get()
pellow.setValue(msg)
return 0
def childfunc():
for i in range(1,100):
progressQueue.put(i)
Here the problem is that, the while loop freezes the mainwindow/gui app from interaction. Can anyone suggest, what do I need to do here?
A complete working example:
import queue
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from threading import Thread
app = QApplication(sys.argv)
class mainwindow1(QMainWindow):
def __init__(self):
super().__init__()
mainwindow = QWidget()
mainwindow.setObjectName("mainwindow")
mainwindow.setContentsMargins(100, 100, 100, 100)
self.setCentralWidget(mainwindow)
global pellow
global scanProgressbar
scanProgressbar = QProgressBar()
pellow = QPushButton('Click Here')
pellow.setObjectName("defaultbutton")
cpscanlayout = QVBoxLayout(mainwindow)
cpscanlayout.addWidget(scanProgressbar, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.addWidget(pellow, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.setAlignment(Qt.AlignCenter)
# cpscanlayout.setContentsMargins(0, 0, 0, 0);
cpscanlayout.setSpacing(5);
pellow.setCursor(QCursor(Qt.PointingHandCursor))
pellow.clicked.connect(buttonclicked)
self.show()
app.exec_()
def buttonclicked():
global progressQueue
progressQueue = Queue()
thread = threading.Thread(target=childfunc, daemon=True, name="nemo", args=())
thread.start()
progressQueue.join()
while True:
if not progressQueue.empty():
msg = progressQueue.get()
scanProgressbar.setValue(msg)
return 0

The code has several errors such as imports (import queue but it doesn't look like it uses Queue, it imports Thread but it uses threading.Thread, etc).
On the other hand, the misconception is that in Qt you should not use code that blocks the eventloop, such as while True. If you want to exchange information between threads then in the case of Qt you should not use Queue since it does not notify when there is a new data, instead you must use the signals that are also thread-safe and do notify when there is new data.
import sys
import threading
import time
from PyQt5.QtCore import pyqtSignal, QObject, Qt
from PyQt5.QtGui import QCursor
from PyQt5.QtWidgets import (
QApplication,
QMainWindow,
QProgressBar,
QPushButton,
QVBoxLayout,
QWidget,
)
class Signaller(QObject):
progress_changed = pyqtSignal(int)
class mainwindow1(QMainWindow):
def __init__(self):
super().__init__()
mainwindow = QWidget()
mainwindow.setObjectName("mainwindow")
mainwindow.setContentsMargins(100, 100, 100, 100)
self.setCentralWidget(mainwindow)
scanProgressbar = QProgressBar()
pellow = QPushButton("Click Here")
pellow.setObjectName("defaultbutton")
cpscanlayout = QVBoxLayout(mainwindow)
cpscanlayout.addWidget(scanProgressbar, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.addWidget(pellow, alignment=Qt.AlignCenter, stretch=10)
cpscanlayout.setAlignment(Qt.AlignCenter)
# cpscanlayout.setContentsMargins(0, 0, 0, 0);
cpscanlayout.setSpacing(5)
pellow.setCursor(QCursor(Qt.PointingHandCursor))
pellow.clicked.connect(self.buttonclicked)
self.signaller = Signaller()
self.signaller.progress_changed.connect(scanProgressbar.setValue)
def buttonclicked(self):
thread = threading.Thread(
target=childfunc, args=(self.signaller,), daemon=True, name="nemo"
)
thread.start()
def childfunc(signaller):
for i in range(1, 100):
signaller.progress_changed.emit(i)
time.sleep(0.1)
app = QApplication(sys.argv)
w = mainwindow1()
w.show()
app.exec_()

Related

Start a process on click

I am trying to add multiprocessing to my project. To exemplify, I made a little project with just a button, and when the button is pressed, I want to start a process with a method, but the method is never called.
How could I call the method within a process?
Here is my code:
from multiprocessing import Process
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Python ")
self.setGeometry(100, 100, 600, 400)
self.UiComponents()
self.show()
def UiComponents(self):
button = QPushButton("CLICK", self)
button.setGeometry(200, 150, 100, 30)
proc = Process(target=self.button_clicked)
procs = []
procs.append(proc)
button.clicked.connect(proc.start)
def button_clicked(self):
print("pressed")
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
generally mixing both Qt GUI objects and anything that allows threading/multiprocessesing is not safe, and will either not work or cause a segmentation fault.
the correct way to do it without crashing your application is to move the target function to be a function that doesn't relate to QT GUI. (not a member of any Qt GUI object or has a reference to any Qt GUI object)
also processes can only start once, so you need to create one on every button press as follows:
from multiprocessing import Process
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
import sys
def some_function():
print("hello")
class Window(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Python ")
self.setGeometry(100, 100, 600, 400)
self.UiComponents()
self.show()
def UiComponents(self):
button = QPushButton("CLICK", self)
button.setGeometry(200, 150, 100, 30)
self.procs = []
button.clicked.connect(self.button_clicked)
def button_clicked(self):
proc = Process(target=some_function)
self.procs.append(proc)
proc.start()
if __name__ == "__main__": # for windows compatibility
App = QApplication(sys.argv)
window = Window()
sys.exit(App.exec())
the button is connected to a function which belongs to a Qt object, but the process is connected to a function that is not in any way connected to Qt, and will therefore not crash your application.

Periodic task in Qt without interrupting events

I have a graphic application in python using QWidget. The user can change elements by moving the mouse (using mouseMoveEvent), and the program should periodically (e.g., once per second) compute a function update_forces based on these elements.
Problem: the mouseMoveEvent doesn't trigger as often while update_forces is computed, so the program becomes periodically unresponsive. There are various tutorials online on how to remedy this using threads, but their solutions don't work for me, and I don't know why.
MWE
# Imports
import sys
import random
import time
from PyQt5.QtCore import QThread, pyqtSignal, QObject
from PyQt5.QtWidgets import QWidget, QApplication
# update_forces dummy
def update_forces():
for i in range(10000000):
x = i**2
# Worker object:
class Worker(QObject):
finished = pyqtSignal()
def run(self):
while(True):
update_forces()
time.sleep(1)
# and the Window class with a dummy mouseEvent that just prints a random number
class Window(QWidget):
def __init__(self):
super().__init__()
self.initUI()
self.setMouseTracking(True)
def initUI(self):
self.setGeometry(20, 20, 500, 500)
self.show()
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.thread.started.connect(self.worker.run)
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
def mouseMoveEvent(self, e):
print(random.random())
# head procedure
if __name__ == '__main__':
app = QApplication(sys.argv)
w = Window()
w.show()
sys.exit(app.exec_())
When I run this (Windows 10) and move my cursor around on the screen, I can clearly observe two phases: it goes from printing numbers rapidly (while update_forces is not computed), to printing them much more slowly (while it is computed). It keeps alternating between both.
The solution is to use multiprocessing rather than threading.
from multiprocessing import Process
if __name__ == '__main__':
p = Process(target=updater, args=())
p.start()
def updater(queue,test):
while(True):
update_forces()
time.sleep(1)
The problem with this is that no data is shared between processes by default, but that's solvable.

Why is my loading screen not showing up using QThread?

I'm making a desktop application in which once databases are being loaded I want to display a loading screen. A simple search led me to use gif files in QLabel with QThread object. But in my case QThread will not show anything.
The thread works fine, but there is something wrong with my implementation, and I cannot figure out what. My sample code is as follows:
from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QDialog, QApplication, QPushButton
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QMovie
import sys
import time
class myThread(QThread):
def run(self):
test = QWidget() # Only creating this to give parent to QDialog and QLabel objects in upcoming lines
dialog = QDialog(test)
vbox = QVBoxLayout()
lbl = QLabel(test)
self.moviee = QMovie('Loading.gif')
lbl.setMovie(self.moviee)
self.moviee.start()
vbox.addWidget(lbl)
dialog.setLayout(vbox)
dialog.show()
def stop(self):
self.moviee.stop()
class Main(QWidget):
def __init__(self):
super().__init__()
print('Thread is to be called here...')
thread = myThread()
thread.run()
print('Thread has been called...')
btn= QPushButton('Test button')
vbox = QVBoxLayout()
vbox.addWidget(btn)
self.setLayout(vbox)
time.sleep(5) # sleep function used to emulate funcitons in actual program
# thread.stop()
self.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Main()
sys.exit(app.exec_())
The block of code with QMovie object works fine when it is in the Main loop, so there is definitely somthing wrong with my implementation of QThread.
There are a couple of issues with your code. As #musicamante remarkes, you cannot create widget outside of the main thread. This means you can't create the dialog within myThread.run. Instead you could move the management of the dialog to Main and use slots and signals to open and close the dialog.
Secondly, all time-consuming tasks like your time.sleep(5) should be put in myThread.run.
With this in mind, you could do something like this
from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QDialog, QApplication, QPushButton
from PyQt5.QtCore import QThread
from PyQt5.QtGui import QMovie
import sys
import time
class myThread(QThread):
def run(self):
# time consuming actions
time.sleep(5)
class Main(QWidget):
def __init__(self):
super().__init__()
print('Thread is to be called here...')
self.load()
print('Thread has been called...')
btn= QPushButton('Test button')
vbox = QVBoxLayout()
vbox.addWidget(btn)
self.setLayout(vbox)
self.show()
def load(self):
# setup dialog
dialog = QDialog(self)
vbox = QVBoxLayout()
lbl = QLabel(self)
self.moviee = QMovie('Loading.gif')
lbl.setMovie(self.moviee)
self.moviee.start()
vbox.addWidget(lbl)
dialog.setLayout(vbox)
# setup thread
thread = myThread()
thread.finished.connect(thread.deleteLater)
thread.finished.connect(dialog.close)
thread.finished.connect(dialog.deleteLater)
thread.start()
dialog.exec()
if __name__ == '__main__':
app = QApplication(sys.argv)
window = Main()
app.exec()

Kill program generated by subprocess with PyQt5

This is a program created as an example for explanation.
main.py
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QGridLayout, QPushButton
from worker import Worker
class TestUI(QWidget):
def __init__(self):
super().__init__()
self.worker = Worker()
self.init_ui()
def init_ui(self):
run_btn = QPushButton("Run")
run_btn.clicked.connect(self.run)
kill_btn = QPushButton("Kill")
kill_btn.clicked.connect(self.kill)
layout = QGridLayout()
layout.addWidget(run_btn, 0, 0)
layout.addWidget(kill_btn, 0, 1)
self.setLayout(layout)
def run(self):
self.worker.run_command("calc.exe")
def kill(self):
self.worker.kill_command()
if __name__ == "__main__":
APP = QApplication(sys.argv)
ex = TestUI()
ex.show()
sys.exit(APP.exec_())
worker.py
import os
import signal
import subprocess
from PyQt5.QtCore import QObject
class Worker(QObject):
def __init__(self):
super().__init__()
def run_command(self, cmd):
self.proc = subprocess.Popen(cmd)
def kill_command(self):
# self.proc.kill() => Not working
# self.proc.terminate() => Not working
# os.kill(self.proc.pid, signal.SIGTERM) => Not working
I want to kill the program generated by subprocess when I click the kill button. In other words, I want to send a kill signal to the program like pressing CTRL+C.
And when the main PyQt5 program is terminated, I want the program generated by subprocess to terminate as well.
How can I do this?
Please help me with this problem.
Finally, I found the method.
I used a psutil module.
This is not a built-in module. So you need to install it first.
pip install psutil
And, now you can use this module like below.
def kill_command(self):
for proc in psutil.process_iter():
if "calc" in proc.name().lower():
os.kill(proc.pid, signal.SIGTERM)

QTimer on a Qthread

I have a GUI witch i need to update constantly using a Qtimer, for that I use a worker Qthread, this is my code :
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
from PyQt5.QtCore import QThread, QTimer
import sys
import threading
class WorkerThread(QThread):
def run(self):
print("thread started from :" + str(threading.get_ident()))
timer = QTimer(self)
timer.timeout.connect(self.work)
timer.start(5000)
self.exec_()
def work(self):
print("working from :" + str(threading.get_ident()))
QThread.sleep(5)
class MyGui(QWidget):
worker = WorkerThread()
def __init__(self):
super().__init__()
self.initUi()
print("Starting worker from :" + str(threading.get_ident()))
self.worker.start()
def initUi(self):
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
self.show()
app = QApplication(sys.argv)
gui = MyGui()
app.exec_()
the output is:
Starting worker from :824
thread started from :5916
working from :824
working from :824
the timer is working on the main thread witch freeze my Gui, How can i fix that ?
Answer: in your case I do not see the need to use QThread.
TL; DR;
When do I need to use another thread in the context of a GUI?
Only one thread should be used when some task can block the main thread called the GUI thread, and the blocking is caused because the task is time-consuming, preventing the GUI eventloop from doing its job normally. All modern GUIs are executed in an eventloop that is allows you to receive notifications from the OS like the keyboard, the mouse, etc. and also allows you to modify the status of the GUI depending on the user.
In your case I do not see any heavy task, so I do not see the need for a QThread, I do not really know what the task is that you want to run periodically.
Assuming you have a task that consumes a lot of time, say 30 seconds and you have to do it every half hour, then the thread is necessary. and in your case you want to use a QTimer for it.
Let's go in parts, QTimer is a class that inherits from a QObject, and a QObject belongs to the same as the parent, and if it does not have a parent it belongs to the thread where it was created. On the other hand many times it is thought that a QThread is a thread of Qt, but it is not, QThread is a class that allows to handle the life cycle of a native thread, and that is clearly stated in the docs: The QThread class provides a platform-independent way to manage threads.
Knowing the above, let's analyze your code:
timer = QTimer(self)
In the above code self is the parent of QTimer and self is the QThread, so QTimer belongs to the thread of the parent of QThread or where QThread was created, not to the thread that QThread handles.
Then let's see the code where QThread was created:
worker = WorkerThread()
As we see QThread has no parent, then QThread belongs to the thread where it was created, that is, QThread belongs to the main thread, and consequently its QTimer child also belongs to the main thread. Also note that the new thread that QThread handles only has the scope of the run() method , if the method is elsewhere belongs to the field where QThread was created, with all the above we see that the output of the code is correct, and the QThread.sleep(5) runs on the main thread causing the eventloop to crash and the GUI to freeze.
So the solution is to remove the parent of QTimer so that the thread it belongs to is the one of the run() method, and move the work function within the same method. On the other hand it is a bad practice to create static attributes unnecessarily, considering the above the resulting code is the following:
import sys
import threading
from PyQt5.QtCore import QThread, QTimer
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
class WorkerThread(QThread):
def run(self):
def work():
print("working from :" + str(threading.get_ident()))
QThread.sleep(5)
print("thread started from :" + str(threading.get_ident()))
timer = QTimer()
timer.timeout.connect(work)
timer.start(10000)
self.exec_()
class MyGui(QWidget):
def __init__(self):
super().__init__()
self.initUi()
self.worker = WorkerThread(self)
print("Starting worker from :" + str(threading.get_ident()))
self.worker.start()
def initUi(self):
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
if __name__ == '__main__':
app = QApplication(sys.argv)
gui = MyGui()
gui.show()
sys.exit(app.exec_())
Output:
Starting worker from :140068367037952
thread started from :140067808999168
working from :140067808999168
working from :140067808999168
Observations:
The heavy task that has been emulated is 5 seconds, and that task must be executed every 10 seconds. If your task takes longer than the period you should create other threads.
If your task is to perform a periodic task that is not as heavy as showing time then do not use new threads because you are adding complexity to a simple task, besides this may cause the debugging and testing stage to be more complex.
Sorry, I have misunderstood the question. It might be that this answer on another question might help you. The main message is that you should use the main event loop in Qt to not freeze the GUI instead of executing the thread on __init__:
Pyqt5 qthread + signal not working + gui freeze
You can do that by using Qt slots with the decorator #pyqtSlot().
------------ old (wrong) answer ---------
QTimer can already work on separate threads, so I think you might be able to do it without writing that part yourself. For example you could do what you already do in a function:
def update_gui(self):
# edit: here is where you can add your gui update code:
self.setGeometry(500, 500, 300, 300)
self.pb = QPushButton("Button", self)
self.pb.move(50, 50)
self.show()
# /edit (this is obviously only the setup code, but you get the idea)
self.update_timer = QTimer()
self.update_timer.setInterval(int(5000))
self.update_timer.timeout.connect(self.update_gui)
self.update_timer.start()
and call it in __init__. That's how I implemented some textbox that clears itself after a couple of seconds.
Try it:
import sys
import threading
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class WorkerThread(QThread):
workSignal = pyqtSignal(str)
def run(self):
print("thread started from :" + str(threading.get_ident()))
textLabel = "thread started from :" + str(threading.get_ident())
self.workSignal.emit(textLabel)
self.work()
def work(self):
print("working from :" + str(threading.get_ident()))
textLabel = "working from :" + str(threading.get_ident())
self.workSignal.emit(textLabel)
class MyGui(QWidget):
worker = WorkerThread()
def __init__(self):
super().__init__()
self.initUi()
print("Starting worker from :" + str(threading.get_ident()))
self.lbl.setText("Starting worker from :" + str(threading.get_ident()))
self.worker.workSignal.connect(self.showLabel)
def initUi(self):
self.setGeometry(700, 350, 300, 150)
self.lcdTime = QLCDNumber(self)
self.lcdTime.setSegmentStyle(QLCDNumber.Filled) # Outline Filled Flat
self.lcdTime.setDigitCount(8)
self.timer = QTimer(self)
self.lbl = QLabel(self)
self.pb = QPushButton("Button Close", self, clicked=self.close)
vbox = QVBoxLayout()
vbox.addWidget(self.lcdTime)
vbox.addWidget(self.lbl)
vbox.addWidget(self.pb)
self.setLayout(vbox)
self.timer.timeout.connect(self.showTime)
self.timer.start(1000)
self.numSec = 0
self.show()
def showTime(self):
time = QTime.currentTime()
text = time.toString("hh:mm:ss")
if ((time.second() % 2) == 0):
text = text[0:2] + ' ' + text[3:5] + ' ' + text[6:]
self.lcdTime.display(text)
self.numSec += 1
if self.numSec == 5:
self.worker.start()
self.numSec = 0
def showLabel(self, textLabel):
self.lbl.setText(textLabel)
app = QApplication(sys.argv)
gui = MyGui()
app.exec_()

Categories

Resources