PyQt5 Gui Thread to worker Thread signal/Slot - python

I am trying to make a simple communication between a worker thread, in this case it is called WorkToDo via the PyQt5 Signals and Slots mechanism. I can reliably send data from the Worker to the Gui thread via this mechanism, but I cannot do the same to the gui thread. From my research I have found that this is due to the fact that I have overridden the run function with my own logic. My question, is there any way to manually handle the execution of signals in the worker thread? Is there a better way to accomplish this?
EDIT:
I am actually not overriding run as I do not see run listed in the documentation for QObject. I am mistaken as this is a default function of a QThread object. This is more perplexing to me. Unless I am just further misunderstanding this.
import sys
import time
import qdarkstyle
from PyQt5.QtWidgets import (
QWidget, QLineEdit, QMessageBox, QPushButton, QVBoxLayout, QHBoxLayout, QTabWidget,
QComboBox, QProgressBar, QApplication, QLabel, QGroupBox, QFileDialog, QTextEdit,
QStyleFactory, QFormLayout
)
from PyQt5 import (Qt, QtCore)
from PyQt5.QtCore import *
class Command:
def __init__(self, **kwargs):
self.cmd = kwargs.get('cmd', None)
self.args = kwargs.get('args')
class CommandSignals(QObject):
command = pyqtSignal(Command)
class WorkToDo(QObject):
def __init__(self):
super().__init__()
self.counter = 0
self.signals = CommandSignals()
#QtCore.pyqtSlot(Command)
def process_command(self, command):
"""
#brief process update from gui thread
#param self self
#param command command
#return none
"""
print(f'Update from GUI: {command.__dict__}')
if command.cmd == 'add_to_counter':
self.counter = self.counter + command.args.get('addition', 0)
#pyqtSlot()
def run(self):
"""
#brief actual thread function to run,
started via thread.started signal (why its decorated with a pyqtSlot)
#param self obj ref
#return none
"""
while True:
QThread.sleep(1)
# emit text change signal to main gui thread
cmd = Command(
cmd = 'update_text',
args = {
'text': f'Hello update {self.counter}'
}
)
print(f'Update from worker: {cmd.__dict__}')
self.signals.command.emit(cmd)
self.counter += 1
class Gui(QWidget):
def __init__(self):
super().__init__()
""" make gui elements """
layout = QFormLayout()
self.text_line = QLineEdit()
self.add_button = QPushButton('Add 10 To Counter')
layout.addRow(self.text_line)
layout.addRow(self.add_button)
self.add_button.clicked.connect(self.issue_button_update)
self.signals = CommandSignals()
self.MakeThreadWorker()
""" finalize gui """
self.setLayout(layout)
self.setWindowTitle('Sync Thread Command/Response test')
self.show()
def MakeThreadWorker(self):
self.worker_thread = QThread()
self.worker = WorkToDo()
""" incoming gui update command, works """
self.worker.signals.command.connect(self.process_command)
""" outgoing update to worker thread, does not work """
self.signals.command.connect(self.worker.process_command)
""" signal to start the thread function, works """
self.worker_thread.started.connect(self.worker.run)
self.worker_thread.start()
self.worker.moveToThread(self.worker_thread)
#pyqtSlot(Command)
def process_command(self, command):
"""
#brief process update from work thread
#param self self
#param command command object
#return none
"""
if command.cmd == 'update_text':
text_update = command.args.get('text', '')
self.text_line.setText(text_update)
def issue_button_update(self):
cmd = Command(
cmd = 'add_to_counter',
args = {
'addition': 10
}
)
print('Button Clicked!')
self.signals.command.emit(cmd)
if __name__ == '__main__':
APPLICATION = QApplication([])
APPLICATION.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
gui = Gui()
sys.exit(APPLICATION.exec_())

The problem is that with True while you are blocking the event loop of the secondary thread preventing the signals from being received, if you want to do a periodic task then use a QTimer.
import sys
import time
import qdarkstyle
from PyQt5.QtCore import pyqtSignal, pyqtSlot, QObject, QThread, QTimer
from PyQt5.QtWidgets import QApplication, QFormLayout, QLineEdit, QPushButton, QWidget
class Command:
def __init__(self, **kwargs):
self.cmd = kwargs.get("cmd", None)
self.args = kwargs.get("args")
class CommandSignals(QObject):
command = pyqtSignal(Command)
class WorkToDo(QObject):
def __init__(self):
super().__init__()
self.counter = 0
self.signals = CommandSignals()
self.timer = QTimer(self, timeout=self.run, interval=1000)
#pyqtSlot()
def start(self):
self.timer.start()
#pyqtSlot()
def stop(self):
self.timer.stop()
#pyqtSlot(Command)
def process_command(self, command):
"""
#brief process update from gui thread
#param self self
#param command command
#return none
"""
print(f"Update from GUI: {command.__dict__}")
if command.cmd == "add_to_counter":
self.counter = self.counter + command.args.get("addition", 0)
#pyqtSlot()
def run(self):
print(self.thread(), self.timer.thread())
cmd = Command(cmd="update_text", args={"text": f"Hello update {self.counter}"})
print(f"Update from worker: {cmd.__dict__}")
self.signals.command.emit(cmd)
self.counter += 1
class Gui(QWidget):
def __init__(self):
super().__init__()
""" make gui elements """
layout = QFormLayout()
self.text_line = QLineEdit()
self.add_button = QPushButton("Add 10 To Counter")
layout.addRow(self.text_line)
layout.addRow(self.add_button)
self.add_button.clicked.connect(self.issue_button_update)
self.signals = CommandSignals()
self.MakeThreadWorker()
""" finalize gui """
self.setLayout(layout)
self.setWindowTitle("Sync Thread Command/Response test")
self.show()
def MakeThreadWorker(self):
self.worker_thread = QThread()
self.worker = WorkToDo()
""" incoming gui update command, works """
self.worker.signals.command.connect(self.process_command)
""" outgoing update to worker thread, does not work """
self.signals.command.connect(self.worker.process_command)
""" signal to start the thread function, works """
self.worker_thread.started.connect(self.worker.start)
self.worker_thread.start()
self.worker.moveToThread(self.worker_thread)
#pyqtSlot(Command)
def process_command(self, command):
"""
#brief process update from work thread
#param self self
#param command command object
#return none
"""
if command.cmd == "update_text":
text_update = command.args.get("text", "")
self.text_line.setText(text_update)
def issue_button_update(self):
cmd = Command(cmd="add_to_counter", args={"addition": 10})
print("Button Clicked!")
self.signals.command.emit(cmd)
if __name__ == "__main__":
APPLICATION = QApplication([])
APPLICATION.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
gui = Gui()
sys.exit(APPLICATION.exec_())

Related

SAP, Python and PySide6 - GUI freezes when i execute another class with a long long process

this is the ui_main from my python script:
import ui_nova
from PySide6.QtCore import (QCoreApplication, Signal, QThread, QObject, QRunnable, Slot, QThreadPool)
from PySide6 import QtCore
from PySide6.QtGui import *
from PySide6 import QtWidgets
from PySide6.QtWidgets import (QApplication, QMainWindow, QWidget)
from threading import Thread
import conexoes
import threading
import sys
import traceback
class Sinais(QObject):
finished = Signal()
progress = Signal(int)
class Executora(QThread):
funcao = None
def __init__(self):
super(Executora, self).__init__()
self.sinais = Sinais()
self.funcaoaExecutar = None
self.finished = self.sinais.finished
self.progress = self.sinais.progress
def nomeDaFuncao(self, funcao):
self.funcaoaExecutar = funcao
#Slot()
def runner(self, funcao):
processo = conexoes.Conexoes()
aExecutar = funcao
execute = eval(f'processo.{aExecutar}')
try:
execute()
except:
traceback.print_exc()
exctype, value = sys.exc_info()[:2]
self.signals.error.emit((exctype, value, traceback.format_exc()))
else:
print("rodou")
finally:
self.finished.emit()
class UIMainConexao(QMainWindow, ui_nova.Ui_MainWindow):
def __init__(self):
super(UIMainConexao, self).__init__()
self.setupUi(self)
self.MainWW.setWindowFlags(ui_nova.QtCore.Qt.FramelessWindowHint)
self.MainWW.setAttribute(ui_nova.Qt.WA_TranslucentBackground)
self.buttonFechar.clicked.connect(self.close)
self.buttonMinimizar.clicked.connect(self.showMinimized)
self.threadpool = QThreadPool()
self.offset = None
print("Multithreading with maximum %d threads" % self.threadpool.maxThreadCount())
# install the event filter on the infoBar widget
self.frameToolBar.installEventFilter(self)
self.buttonHome.clicked.connect(lambda: self.stackedWidget.setCurrentWidget(self.pageInicial))
self.buttonUserConfig.clicked.connect(lambda: self.stackedWidget.setCurrentWidget(self.pageUser))
self.buttonEmpresas.clicked.connect(lambda: self.execute("boletoBancoX"))
def eventFilter(self, source, event):
if source == self.frameToolBar:
if event.type() == ui_nova.QtCore.QEvent.MouseButtonPress:
self.offset = event.pos()
elif event.type() == ui_nova.QtCore.QEvent.MouseMove and self.offset is not None:
# no need for complex computations: just use the offset to compute
# "delta" position, and add that to the current one
self.move(self.pos() - self.offset + event.pos())
# return True to tell Qt that the event has been accepted and
# should not be processed any further
return True
elif event.type() == ui_nova.QtCore.QEvent.MouseButtonRelease:
self.offset = None
# let Qt process any other event
return super().eventFilter(source, event)
#Slot()
def execute(self, funcao):
aExecutar = funcao
self.thread = QThread()
self.worker = Executora()
self.worker.moveToThread(self.thread)
self.thread.started.connect(lambda: print("Iniciou"))
self.thread.started.connect(lambda: self.worker.runner(aExecutar))
self.worker.finished.connect(lambda: print("É PRA FINALIZAR"))
self.worker.finished.connect(self.thread.quit)
self.worker.finished.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
self.thread.start()
This Python project has a long structure with a lot of py files. The exe will contain about 70-100 pages with different process that will be executed ONE AT A TIME.
In "Conexoes" has the conections to the all files and processes that will be executed, so i created a method to link every button(i will add all the buttons connections) to their respective method in conexoes giving just the name using the def execute.
When i start the process, will work and the GUI freezes during the process, but if i use Daemon Threads, the script will run the first steps of the proper function (gui dont freezes), but will crash because him couldnt get the SAPEngineScript.
I already tried read many sites how to use Threads in python and put in code, but all didnt work properly. I really dont know what i do.
So, after much more search, i found the solution, which i think can be very usefully for everyone who use QT with SAP.
Basicly, when you start a sap function using threading, you will receive an error about the Object SAPGUI, so the solution for this is just import pythoncom for your code and insert "pythoncom.CoInitialize()" before the line getObject("SAPGUI").
Now im using Daemon Thread to execute the function without freezes de GUI.
Ex:
import win32com.client
import pythoncom
def processoSAP(self):
try:
pythoncom.CoInitialize()
self.SapGuiAuto = win32com.client.GetObject("SAPGUI")
if not type(self.SapGuiAuto) == win32com.client.CDispatch:
return
self.application = self.SapGuiAuto.GetScriptingEngine
except NameError:
print(NameError)
Where i found: https://django.fun/en/qa/44126/

Emit signals from Worker threads to Main window and back using #pyqtSlot decorators

I'm currently learning to build GUI's using PyQt5 and here is my problem:
I have 2 Threads. The first one is QThread object which generates some data.
The other one is QObject function which runs in QThread using .moveToThread method and processes data received from my so-called data generator. On main window I emit signal to do some stuff with value returned from generator separately from main thread in order to avoid freezing GUI.
The processing looks like:
receive data;
append it to list;
if list is full enough to make some calculations -> emit the result back to GUI;
wait some time (e.g. 3 secs) and start again the reception of data.
The problem is in the last point. I freeze the thread after calculations on list items, but it does not block signal emitted from the GUI, so when time.sleep(3) is up, my list already contains 3 items, but I need to start it from 0.
Commented fragments of code are my thoughts on how to do it using #pyqtSlot decorators and QtCore.pyqtSignal() but it doesn`t work.
Let me know if any clarification is needed, maybe I missed something.
Will be glad for any help.
Here is my code:
import sys
from PyQt5.QtWidgets import QWidget, QPushButton, QApplication, QGridLayout, QLabel
from PyQt5.QtCore import QObject, QThread, pyqtSlot
from PyQt5 import QtCore
import random
import time
class DataThread(QThread):
output = QtCore.pyqtSignal(list)
def __init__(self):
super().__init__()
self._run_flag = True
def run(self):
while self._run_flag:
value = round(random.random(), 4)
value_x2 = value * 2
values = [value, value_x2]
self.output.emit(values)
# print(value)
time.sleep(1)
def stop(self):
self._run_flag = False
self.quit()
class CalculationsThread(QObject):
# accepting signals from main data generator thread
calculations_result = QtCore.pyqtSignal(float)
calc_value = QtCore.pyqtSignal(float)
# emitting signal from this thread
kill = QtCore.pyqtSignal()
deny_access = QtCore.pyqtSignal(bool)
def __init__(self, parent=None):
super(CalculationsThread, self).__init__(parent)
self.list_of_heights = []
#pyqtSlot(float)
def work(self, value):
# self.deny_access.emit(True)
self.list_of_heights.append(value)
self.calculations_result.emit(len(self.list_of_heights))
if len(self.list_of_heights) == 10:
result = sum(self.list_of_heights) / len(self.list_of_heights)
self.calculations_result.emit(result)
self.list_of_heights = []
# self.deny_access.emit(False)
time.sleep(3)
class WinForm(QWidget):
variable_value = QtCore.pyqtSignal(float)
def __init__(self, parent=None):
super(WinForm, self).__init__(parent)
self.setWindowTitle('QThreads example')
self.change_flag = 0
self.calculations_flag = 0
self.list_from_data_generator_thread = []
self.thread = None
self.calculations_worker = None
self.thread1 = None
self.label = QLabel('Label')
self.startBtn = QPushButton('Start')
self.startBtn.clicked.connect(self.start_data_generator)
self.endBtn = QPushButton('Stop')
self.endBtn.clicked.connect(self.stop_data_generator)
self.output1_btn = QPushButton("Value 1")
self.output1_btn.clicked.connect(self.show_value_1)
self.output2_btn = QPushButton("Value 2")
self.output2_btn.clicked.connect(self.show_value_2)
self.calculations_label = QLabel("Calculations result: ")
self.calculations_button = QPushButton("Start calculations")
self.calculations_button.clicked.connect(self.start_calculations)
layout = QGridLayout()
layout.addWidget(self.label, 0, 0)
layout.addWidget(self.startBtn, 1, 0)
layout.addWidget(self.endBtn, 1, 1)
layout.addWidget(self.output1_btn, 2, 0)
layout.addWidget(self.output2_btn, 2, 1)
layout.addWidget(self.calculations_label, 3, 0)
layout.addWidget(self.calculations_button, 3, 1)
self.setLayout(layout)
#pyqtSlot(list)
# #pyqtSLot(bool)
def set_label_text(self, value):
# if access_flag:
self.variable_value.emit(value[1])
# if not access flag:
# pass
if self.change_flag == 0:
self.label.setText(f"1: {value[0]}")
if self.change_flag == 1:
self.label.setText(f"2: {value[1]}")
def start_data_generator(self):
self.startBtn.setEnabled(False)
self.endBtn.setEnabled(True)
self.thread = DataThread()
self.thread.output.connect(self.set_label_text)
self.thread.start()
self.endBtn.clicked.connect(self.thread.stop)
def stop_data_generator(self):
self.thread.output.disconnect(self.set_label_text)
self.startBtn.setEnabled(True)
self.endBtn.setEnabled(False)
#pyqtSlot(float)
def output_calculations(self, value):
self.calculations_label.setText(f"Calculations result: {value}")
def start_calculations(self):
self.calculations_worker = CalculationsThread()
self.thread1 = QThread()
self.calculations_worker.moveToThread(self.thread1)
self.variable_value.connect(self.calculations_worker.work)
self.calculations_worker.calculations_result.connect(self.output_calculations)
self.calculations_worker.kill.connect(self.thread1.quit)
self.thread1.start()
self.calculations_button.clicked.disconnect(self.start_calculations)
self.calculations_button.setText("Stop calculations")
self.calculations_button.clicked.connect(self.thread1.quit)
self.calculations_button.clicked.connect(self.stop_calculations)
def stop_calculations(self):
self.calculations_worker.calculations_result.disconnect(self.output_calculations)
self.calculations_button.clicked.disconnect(self.stop_calculations)
self.calculations_button.clicked.disconnect(self.thread1.quit)
self.calculations_button.setText("Start calculations")
self.calculations_button.clicked.connect(self.start_calculations)
def show_value_1(self):
self.change_flag = 0
def show_value_2(self):
self.change_flag = 1
if __name__ == '__main__':
app = QApplication(sys.argv)
form = WinForm()
form.show()
sys.exit(app.exec_())

Running multiple background threads using QThread in PyQt5

Having an issue calling multiple threads. I have no luck. Runs the first definition (procCounter) fine but does not display or run the second (procCounter2)
Below is my main and my worker:
# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout
import sys
import worker
class Form(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0")
self.label2 = QLabel("1")
# 1 - create Worker and Thread inside the Form
self.obj = worker.Worker() # no parent!
self.thread = QThread() # no parent!
# 2 - Connect Worker`s Signals to Form method slots to post data.
self.obj.intReady.connect(self.onIntReady)
self.obj.intReady.connect(self.onIntReady2)
# 3 - Move the Worker object to the Thread object
self.obj.moveToThread(self.thread)
# 4 - Connect Worker Signals to the Thread slots
self.obj.finished.connect(self.thread.quit)
# 5 - Connect Thread started signal to Worker operational slot method
self.thread.started.connect(self.obj.procCounter)
self.thread.started.connect(self.obj.procCounter2)
# * - Thread finished signal will close the app if you want!
#self.thread.finished.connect(app.exit)
# 6 - Start the thread
self.thread.start()
# 7 - Start the form
self.initUI()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,0)
grid.addWidget(self.label2,0,1)
self.move(300, 150)
self.setWindowTitle('thread test')
self.show()
def onIntReady(self, i):
self.label.setText("{}".format(i))
print(i)
def onIntReady2(self, i):
#self.label2.setText("{}".format(i))
print(i)
app = QApplication(sys.argv)
form = Form()
sys.exit(app.exec_())
Here is my worker:
# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
#pyqtSlot()
def procCounter(self): # A slot takes no params
for i in range(1, 100):
time.sleep(.5)
self.intReady.emit(i)
self.finished.emit()
#pyqtSlot(int)
def procCounter2(self): # A slot takes no params
for i in range(1000):
time.sleep(.2)
self.intReady.emit(i)
self.finished.emit()
I have even tried adding n additional pyqtSignal such as: "intReady2 = pyqtSignal(int)" in the worker and then in the main adding " self.obj.intReady2.connect(self.onIntReady2)" But still no luck.
"obj" lives in the same thread so when invoking the slots they will execute the same thread so the first function it executes will block the other. The solution is to create 2 workers that live in different threads
class Worker(QObject):
finished = pyqtSignal()
intReady = pyqtSignal(int)
#pyqtSlot()
def procCounter(self):
pass
class Worker1(Worker):
#pyqtSlot()
def procCounter(self):
for i in range(1, 100):
time.sleep(.5)
self.intReady.emit(i)
self.finished.emit()
class Worker2(QObject):
#pyqtSlot(int)
def procCounter(self):
for i in range(1000):
time.sleep(.2)
self.intReady.emit(i)
self.finished.emit()
self.obj1 = worker.Worker1()
self.thread1 = QThread()
self.obj1.moveToThread(self.thread1)
self.obj1.intReady.connect(self.onIntReady)
self.thread1.started.connect(self.obj.procCounter)
self.obj2 = worker.Worker2()
self.thread2 = QThread()
self.obj2.moveToThread(self.thread2)
self.obj2.intReady.connect(self.onIntReady2)
self.thread2.started.connect(self.obj2.procCounter)
self.thread1.start()
self.thread2.start()
The problem is self.thread.started.connect(self.obj.procCounter), you need to replace it with self.thread.started.connect(lambda: self.obj.procCounter())
More info here

How to use Threading with variables inside a function? PyQt5

I have a big function which freezes my PyQt5 program, I tried to use a different thread for it (I use QThread for that). Problem is my function needs some variables to work properly.
How to make this works? I show what I did.
Original code:
class AnalysisWindow(QtWidgets.QMainWindow):
def __init__(self, firstWindow):
super(AnalysisWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.letsgo)
def letsgo(self):
#here some code , not big
#and at some point i have a heavy one which make it freeze until it's over:
self.x1, self.x2, self.y1,self.y2, self.z, = self.analyze(self.i1, self.i2, self.i3)
def analyze(self,i1,i2,i3):
#big function
return(x1,x2,y1,y2,z)
what I tried :
from PyQt5.QtCore import Qt, QThread, pyqtSignal
class AnalysisWindow(QtWidgets.QMainWindow):
class MyThread(QThread):
_signal =pyqtSignal()
def __init__(self):
super().__init__()
def run(self,i1,i2,i3): # here I obviously can't put variables
#I copied here my analyze function
return(x1,x2,y1,y2,z)
self._signal.emit()
def __init__(self, firstWindow):
super(AnalysisWindow, self).__init__()
self.ui = Ui_MainWindow()
self.ui.setupUi(self)
self.ui.pushButton.clicked.connect(self.letsgo)
def letsgo(self):
self.thread = MyThread()
self.thread.start()
#here I dont see how to send the variables self.i1, self.i2, self.i3 and how to get the result: x1,x2,y1,y2,z
I created the thread class inside the QMainWindow class because i need to pass some variables (self.i1, self.i2, self.i3) from QMainWindow to the function which will use the new thread. Maybe that's bad, but it doesn't work in any way. Thanks everyone.
Here is a minimal working example that you can adapt it to your code.
Few things to note:
You should not inherit from QThread. Instead, you should create a worker and move it into your thread.
In worker, instead of trying to return a result, emit the signal that holds the result and process that signal in your application.
Similarly, instead of trying to call your worker normally, communicate it through its slots via QtCore.QMetaObject.invokeMethod. Once your thread is started, you can call this method as much as you want.
Refer to this answer for more
import sys
import random
from PyQt5.QtCore import QThread, pyqtSignal, QObject, pyqtSlot, Qt
from PyQt5 import QtWidgets
from PyQt5 import QtCore
class Analyzer(QObject):
analyze_completed = pyqtSignal(bool)
analyze_result = pyqtSignal(list, int)
#pyqtSlot(str, list)
def analyze(self, foo, analyze_args):
print(foo, analyze_args)
self.analyze_completed.emit(False)
# do your heavy calculations
for i in range(10000000):
x = i ** 0.5
result = sum(analyze_args)
self.analyze_result.emit(analyze_args, result)
self.analyze_completed.emit(True)
class AnalysisWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.label = QtWidgets.QLabel("")
self.i = 0
self.label_i = QtWidgets.QLabel("Value of i: {}".format(self.i))
self.increment_button = QtWidgets.QPushButton("increment i")
self.pushbutton = QtWidgets.QPushButton("Analyze")
super(AnalysisWindow, self).__init__()
self.analyze_args = []
self.analyzer = Analyzer()
self.thread = QThread()
self.analyzer.analyze_result.connect(self.on_analyze_result_ready)
self.analyzer.analyze_completed.connect(self.on_analyze_completed)
self.analyzer.moveToThread(self.thread)
self.thread.start()
self.init_UI()
def init_UI(self):
grid = QtWidgets.QGridLayout()
grid.addWidget(self.label, 0, 0)
grid.addWidget(self.pushbutton)
grid.addWidget(self.label_i)
grid.addWidget(self.increment_button)
self.increment_button.clicked.connect(self.increment_i)
self.pushbutton.clicked.connect(self.start_analyze)
self.setLayout(grid)
self.move(300, 150)
self.setMinimumSize(300, 100)
self.setWindowTitle('Thread Test')
self.show()
def start_analyze(self):
self.analyze_args.clear()
self.analyze_args.extend(random.choices(range(100), k=5))
QtCore.QMetaObject.invokeMethod(self.analyzer, 'analyze', Qt.QueuedConnection,
QtCore.Q_ARG(str, "Hello World!"),
QtCore.Q_ARG(list, self.analyze_args))
def increment_i(self):
self.i += 1
self.label_i.setText("Value of i: {}".format(self.i))
def on_analyze_result_ready(self, args, result):
t = "+".join(str(i) for i in args)
self.label.setText(f"{t} = {result}")
def on_analyze_completed(self, completed):
if completed:
self.label.setStyleSheet('color: blue')
else:
self.label.setText(
"Analyzing... {}".format(", ".join(str(i) for i in self.analyze_args)))
self.label.setStyleSheet('color: yellow')
app = QtWidgets.QApplication(sys.argv)
widget = AnalysisWindow()
sys.exit(app.exec_())
Hope this helps!

PyQt5 QThread not working, gui still freezing

I have this code (if you have pyqt5, you should be able to run it yourself):
import sys
import time
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
class Worker(QObject):
def __init__(self):
super().__init__()
self.thread = None
class Tab(QObject):
def __init__(self, _main):
super().__init__()
self._main = _main
class WorkerOne(Worker):
finished = pyqtSignal()
def __init__(self):
super().__init__()
#pyqtSlot(str)
def print_name(self, name):
for _ in range(100):
print("Hello there, {0}!".format(name))
time.sleep(1)
self.finished.emit()
self.thread.quit()
class SomeTabController(Tab):
def __init__(self, _main):
super().__init__(_main)
self.threads = {}
self._main.button_start_thread.clicked.connect(self.start_thread)
# Workers
self.worker1 = WorkerOne()
#self.worker2 = WorkerTwo()
#self.worker3 = WorkerThree()
#self.worker4 = WorkerFour()
def _threaded_call(self, worker, fn, *args, signals=None, slots=None):
thread = QThread()
thread.setObjectName('thread_' + worker.__class__.__name__)
# store because garbage collection
self.threads[worker] = thread
# give worker thread so it can be quit()
worker.thread = thread
# objects stay on threads after thread.quit()
# need to move back to main thread to recycle the same Worker.
# Error is thrown about Worker having thread (0x0) if you don't do this
worker.moveToThread(QThread.currentThread())
# move to newly created thread
worker.moveToThread(thread)
# Can now apply cross-thread signals/slots
#worker.signals.connect(self.slots)
if signals:
for signal, slot in signals.items():
try:
signal.disconnect()
except TypeError: # Signal has no slots to disconnect
pass
signal.connect(slot)
#self.signals.connect(worker.slots)
if slots:
for slot, signal in slots.items():
try:
signal.disconnect()
except TypeError: # Signal has no slots to disconnect
pass
signal.connect(slot)
thread.started.connect(lambda: fn(*args)) # fn needs to be slot
thread.start()
#pyqtSlot()
def _receive_signal(self):
print("Signal received.")
#pyqtSlot(bool)
def start_thread(self):
name = "Bob"
signals = {self.worker1.finished: self._receive_signal}
self._threaded_call(self.worker1, self.worker1.print_name, name,
signals=signals)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Thread Example")
form_layout = QVBoxLayout()
self.setLayout(form_layout)
self.resize(400, 400)
self.button_start_thread = QPushButton()
self.button_start_thread.setText("Start thread.")
form_layout.addWidget(self.button_start_thread)
self.controller = SomeTabController(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
_main = MainWindow()
_main.show()
sys.exit(app.exec_())
However WorkerOne still blocks my GUI thread and the window is non-responsive when WorkerOne.print_name is running.
I have been researching a lot about QThreads recently and I am not sure why this isn't working based on the research I've done.
What gives?
The problem is caused by the connection with the lambda method since this lambda is not part of the Worker so it does not run on the new thread. The solution is to use functools.partial:
from functools import partial
...
thread.started.connect(partial(fn, *args))
Complete Code:
import sys
import time
from functools import partial
from PyQt5.QtWidgets import QApplication, QPushButton, QVBoxLayout, QWidget
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
class Worker(QObject):
def __init__(self):
super().__init__()
self.thread = None
class Tab(QObject):
def __init__(self, _main):
super().__init__()
self._main = _main
class WorkerOne(Worker):
finished = pyqtSignal()
def __init__(self):
super().__init__()
#pyqtSlot(str)
def print_name(self, name):
for _ in range(100):
print("Hello there, {0}!".format(name))
time.sleep(1)
self.finished.emit()
self.thread.quit()
class SomeTabController(Tab):
def __init__(self, _main):
super().__init__(_main)
self.threads = {}
self._main.button_start_thread.clicked.connect(self.start_thread)
# Workers
self.worker1 = WorkerOne()
#self.worker2 = WorkerTwo()
#self.worker3 = WorkerThree()
#self.worker4 = WorkerFour()
def _threaded_call(self, worker, fn, *args, signals=None, slots=None):
thread = QThread()
thread.setObjectName('thread_' + worker.__class__.__name__)
# store because garbage collection
self.threads[worker] = thread
# give worker thread so it can be quit()
worker.thread = thread
# objects stay on threads after thread.quit()
# need to move back to main thread to recycle the same Worker.
# Error is thrown about Worker having thread (0x0) if you don't do this
worker.moveToThread(QThread.currentThread())
# move to newly created thread
worker.moveToThread(thread)
# Can now apply cross-thread signals/slots
#worker.signals.connect(self.slots)
if signals:
for signal, slot in signals.items():
try:
signal.disconnect()
except TypeError: # Signal has no slots to disconnect
pass
signal.connect(slot)
#self.signals.connect(worker.slots)
if slots:
for slot, signal in slots.items():
try:
signal.disconnect()
except TypeError: # Signal has no slots to disconnect
pass
signal.connect(slot)
thread.started.connect(partial(fn, *args)) # fn needs to be slot
thread.start()
#pyqtSlot()
def _receive_signal(self):
print("Signal received.")
#pyqtSlot(bool)
def start_thread(self):
name = "Bob"
signals = {self.worker1.finished: self._receive_signal}
self._threaded_call(self.worker1, self.worker1.print_name, name,
signals=signals)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("Thread Example")
form_layout = QVBoxLayout()
self.setLayout(form_layout)
self.resize(400, 400)
self.button_start_thread = QPushButton()
self.button_start_thread.setText("Start thread.")
form_layout.addWidget(self.button_start_thread)
self.controller = SomeTabController(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
_main = MainWindow()
_main.show()
sys.exit(app.exec_())
To avoid blocking the gui for a slideshow function showing images i run the following
simple code.
A background Thread Class to wait one second, then signal to the gui waiting is finished.
class Waiter(QThread):
result = pyqtSignal(object)
def __init__(self):
QtCore.QThread.__init__(self)
def run(self):
while self.isRunning:
self.sleep(1)
self.result.emit("waited for 1s")
Then in main window connect the slideshow start and stop button to start and stop methods of main app and connect the nextImage function to the signal the Waiter Thread emits.
self.actionstopSlideShow.triggered.connect(self.stopSlideShow)
self.actionslideShowStart.triggered.connect(self.startSlideShow)
self.waitthread = Waiter()
self.waitthread.result.connect(self.nextImage)
Then two methods of main app allow to start and stop the time ticker
def startSlideShow(self):
"""Start background thread that waits one second,
on wait result trigger next image
use thread otherwise gui freezes and stop button cannot be pressed
"""
self.waitthread.start()
def stopSlideShow(self):
self.waitthread.terminate()
self.waitthread.wait()
Up to now i have no problems subclassing from QThread in pyqt5, gui changes are all handled inside the main (gui) thread.

Categories