PyQt: moveToThread does not work when using partial() for slot - python

I am building a small GUI application which runs a producer (worker) and the GUI consumes the output on demand and plots it (using pyqtgraph).
Since the producer is a blocking function (takes a while to run), I (supposedly) moved it to its own thread.
When calling QThread.currentThreadId() from the producer it outputs the same number as the main GUI thread. So, the worker is executed first, and then all the plotting function calls are executed (because they are being queued on the same thread's event queue). How can I fix this?
Example run with partial:
gui thread id 140665453623104
worker thread id: 140665453623104
Here is my full code:
from PyQt4 import QtCore, QtGui
from PyQt4.QtCore import pyqtSignal
import pyqtgraph as pg
import numpy as np
from functools import partial
from Queue import Queue
import math
import sys
import time
class Worker(QtCore.QObject):
termino = pyqtSignal()
def __init__(self, q=None, parent=None):
super(Worker, self).__init__(parent)
self.q = q
def run(self, m=30000):
print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
for x in xrange(m):
#y = math.sin(x)
y = x**2
time.sleep(0.001) # Weird, plotting stops if this is not present...
self.q.put((x,y,y))
print('Worker finished')
self.termino.emit()
class MainWindow(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.q = Queue()
self.termino = False
self.worker = Worker(self.q)
self.workerThread = None
self.btn = QtGui.QPushButton('Start worker')
self.pw = pg.PlotWidget(self)
pi = self.pw.getPlotItem()
pi.enableAutoRange('x', True)
pi.enableAutoRange('y', True)
self.ge1 = pi.plot(pen='y')
self.xs = []
self.ys = []
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.pw)
layout.addWidget(self.btn)
self.resize(400, 400)
def run(self):
self.workerThread = QtCore.QThread()
self.worker.moveToThread(self.workerThread)
self.worker.termino.connect(self.setTermino)
# moveToThread doesn't work here
self.btn.clicked.connect(partial(self.worker.run, 30000))
# moveToThread will work here
# assume def worker.run(self): instead of def worker.run(self, m=30000)
# self.btn.clicked.connect(self.worker.run)
self.btn.clicked.connect(self.graficar)
self.workerThread.start()
self.show()
def setTermino(self):
self.termino = True
def graficar(self):
if not self.q.empty():
e1,e2,ciclos = self.q.get()
self.xs.append(ciclos)
self.ys.append(e1)
self.ge1.setData(y=self.ys, x=self.xs)
if not self.termino:
QtCore.QTimer.singleShot(1, self.graficar)
if __name__ == '__main__':
app = QtGui.QApplication([])
window = MainWindow()
QtCore.QTimer.singleShot(0, window.run);
sys.exit(app.exec_())

The problem is that Qt attempts to choose the connection type (when you call signal.connect(slot)) based on the what thread the slot exists in. Because you have wrapped the slot in the QThread with partial, the slot you are connecting to resides in the MainThread (the GUI thread). You can override the connection type (as the second argument to connect() but that doesn't help because the method created by partial will always exist in the MainThread, and so setting the connection type to by Qt.QueuedConnection doesn't help.
The only way around this that I can see is to set up a relay signal, the sole purpose of which is to effectively change an emitted signal with no arguments (eg the clicked signal from a button) to a signal with one argument (your m parameter). This way you don't need to wrap the slot in the QThread with partial().
The code is below. I've created a signal with one argument (an int) called 'relay' in the main windows class. The button clicked signal is connected to a method within the main window class, and this method has a line of code which emits the custom signal I created. You can extend this method (relay_signal()) to get the integer to pass to the QThread as m (500 in this case), from where ever you like!
So here is the code:
from functools import partial
from Queue import Queue
import math
import sys
import time
class Worker(QtCore.QObject):
termino = pyqtSignal()
def __init__(self, q=None, parent=None):
super(Worker, self).__init__(parent)
self.q = q
def run(self, m=30000):
print('worker thread id: {}'.format(QtCore.QThread.currentThreadId()))
for x in xrange(m):
#y = math.sin(x)
y = x**2
#time.sleep(0.001) # Weird, plotting stops if this is not present...
self.q.put((x,y,y))
print('Worker finished')
self.termino.emit()
class MainWindow(QtGui.QWidget):
relay = pyqtSignal(int)
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.q = Queue()
self.termino = False
self.worker = Worker(self.q)
self.workerThread = None
self.btn = QtGui.QPushButton('Start worker')
self.pw = pg.PlotWidget(self)
pi = self.pw.getPlotItem()
pi.enableAutoRange('x', True)
pi.enableAutoRange('y', True)
self.ge1 = pi.plot(pen='y')
self.xs = []
self.ys = []
layout = QtGui.QVBoxLayout(self)
layout.addWidget(self.pw)
layout.addWidget(self.btn)
self.resize(400, 400)
def run(self):
self.workerThread = QtCore.QThread()
self.worker.termino.connect(self.setTermino)
self.worker.moveToThread(self.workerThread)
# moveToThread doesn't work here
# self.btn.clicked.connect(partial(self.worker.run, 30000))
# moveToThread will work here
# assume def worker.run(self): instead of def worker.run(self, m=30000)
#self.btn.clicked.connect(self.worker.run)
self.relay.connect(self.worker.run)
self.btn.clicked.connect(self.relay_signal)
self.btn.clicked.connect(self.graficar)
self.workerThread.start()
self.show()
def relay_signal(self):
self.relay.emit(500)
def setTermino(self):
self.termino = True
def graficar(self):
if not self.q.empty():
e1,e2,ciclos = self.q.get()
self.xs.append(ciclos)
self.ys.append(e1)
self.ge1.setData(y=self.ys, x=self.xs)
if not self.termino or not self.q.empty():
QtCore.QTimer.singleShot(1, self.graficar)
if __name__ == '__main__':
app = QtGui.QApplication([])
window = MainWindow()
QtCore.QTimer.singleShot(0, window.run);
sys.exit(app.exec_())
I also modified the graficar method to continue plotting (even after the thread is terminated) if there is still data in the queue. I think this might be why you needed the time.sleep in the QThread, which is now also removed.
Also regarding your comments in the code on where to place moveToThread, where it is now is correct. It should be before the call that connects the QThread slot to a signal, and the reason for this is discussed in this stack-overflow post: PyQt: Connecting a signal to a slot to start a background operation

Related

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_())

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.

using threading for a qt application

I have the following two files:
import sys
import time
from PyQt4 import QtGui, QtCore
import btnModule
class WindowClass(QtGui.QWidget):
def __init__(self):
super(WindowClass, self).__init__()
self.dataLoaded = None
# Widgets
# Buttons
thread = WorkerForLoop(self.runLoop)
# thread.start()
self.playBtn = btnModule.playpauselBtnClass \
('Play', thread.start)
# Layout
layout = QtGui.QHBoxLayout()
layout.addWidget(self.playBtn)
self.setLayout(layout)
# Window Geometry
self.setGeometry(100, 100, 100, 100)
def waitToContinue(self):
print self.playBtn.text()
while (self.playBtn.text() != 'Pause'):
pass
def runLoop(self):
for ii in range(100):
self.waitToContinue()
print 'num so far: ', ii
time.sleep(0.5)
class WorkerForLoop(QtCore.QThread):
def __init__(self, function, *args, **kwargs):
super(WorkerForLoop, self).__init__()
self.function = function
self.args = args
self.kwargs = kwargs
def __del__(self):
self.wait()
def run(self):
print 'let"s run it'
self.function(*self.args, **self.kwargs)
return
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
wmain = WindowClass()
wmain.show()
sys.exit(app.exec_())
and the second file btnModule.py:
from PyQt4 import QtGui, QtCore
class playpauselBtnClass(QtGui.QPushButton):
btnSgn = QtCore.pyqtSignal()
def __init__(self, btnName, onClickFunction):
super(playpauselBtnClass, self).__init__(btnName)
self.clicked.connect(self.btnPressed)
self.btnSgn.connect(onClickFunction)
def btnPressed(self):
if self.text() == 'Play':
self.setText('Pause')
self.btnSgn.emit()
print 'Changed to pause and emited signal'
elif self.text() == 'Pause':
self.setText('Continue')
print 'Changed to Continue'
elif self.text() == 'Continue':
self.setText('Pause')
print 'Changed to Pause'
If in the first file I remove the comment at thread.start() it works as expected, it starts the thread and then hangs until I click Play on the UI. However, I thought it should work even if I didn't start it there as the signal is btnSgn is connected to onClickFunction which in this case takes the value thread.start.
Used this as a reference
http://joplaete.wordpress.com/2010/07/21/threading-with-pyqt4/
I think the reason it doesn't work when you try to call thread.start() in your second file (via self.btnSgn.emit()) is that the thread object goes out of scope outside of the init function where you created it. So you are calling start() on an already deleted thread.
Just changing thread -> self.thread (i.e. making the thread object a member of the WindowClass object) works fine when I tried it, since thread is then kept alive till the end of the program.

Concurrent to QLabel in PyQt

I have i python application, which uses PyQt GUI. It application has some I/O operations, which i want to run in separate threads.
When each thread started, it should write messages to applications main window status bar and when last thread is completed, status bar messages should be cleared.
How can i handle number of threads via QThread?
Here is example of code:
import sys, time
from PyQt4 import QtCore, QtGui
from functools import partial
def io_emulator(sleep_seconds):
print 'We will sleep for %s seconds' % str(sleep_seconds)
time.sleep(sleep_seconds)
class IOThread(QtCore.QThread):
def __init__(self, func):
QtCore.QThread.__init__(self)
self.io_func = func
def run(self):
self.io_func()
class m_Window(QtGui.QWidget):
def __init__(self):
super(m_Window, self).__init__()
self.initUI()
def initUI(self):
self.thread_button = QtGui.QPushButton("Thread", self)
self.thread_button.move(30, 10)
self.spinbox = QtGui.QSpinBox(self)
self.spinbox.move(30, 50)
self.stat_label = QtGui.QLabel("", self)
self.stat_label.setGeometry(QtCore.QRect(200, 200, 150, 14))
self.stat_label.move(30,90)
self.setWindowTitle('Threads')
self.show()
self.thread_button.clicked.connect(self._sleeper)
def _sleeper(self):
seconds = int(self.spinbox.text())
stat_str = 'Sleeping %s seconds' % str(seconds)
io_func = partial(io_emulator, seconds)
set_status_f = partial(self.set_status_msg, stat_str)
self.thread = IOThread(io_func)
self.thread.started.connect(set_status_f)
self.thread.finished.connect(self.clear_status_msg)
self.thread.start()
def set_status_msg(self, msg):
self.stat_label.setText(msg)
def clear_status_msg(self):
self.stat_label.clear()
def main():
app = QtGui.QApplication(sys.argv)
m = m_Window()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
I want, that message is being cleared only when last thread is ended.
You cannot call QWidget functions from any thread but the main thread. If you want another thread to trigger GUI operations, then they should be communicated by emitting signals that are connected to your main thread:
def someFunc():
return "message"
class IOThread(QtCore.QThread):
statusUpdate = QtCore.pyqtSignal(str)
def __init__(self, func):
QtCore.QThread.__init__(self)
self.io_func = func
def run(self):
msg = self.io_func()
self.statusUpdate.emit(msg)
Then in your main thread, connect the io thread to the status label or some intermediate handler:
io_thread = IOThread(someFunc)
io_thread.statusUpdate.connect(self.status_label.setText)
This will create a queued connection, which places the call into the event loop of the main thread for execution.

Categories