PyQt QMutex in worker and main thread - pyqt

I want to lock the print function with a QMutex so only one thread can access it at a time. I wrote a very simple GUI with two buttons, one that quits the thread and one that tries to print something to see if the main thread blocks.
I've gotten it to work by making the lock a global variable but everyone in the internet says global variables are a bad idea. However declaring locks in the __init__ part of the Main Window and Worker class definitions as self.lock = QtCore.QMutex() does not provide the same behavior as when I declare it as a global variable.
What is the proper way to to ensure that the main thread will wait on the worker?
Below is the code for the global case:
import sys
from PyQt5 import QtWidgets, QtCore
import time
lock=QtCore.QMutex()
class expSignals(QtCore.QObject):
# =============================================================================
# Signal for quitting the thread. Dunno if should be seperate class or stati
# for main window
# =============================================================================
pause=QtCore.pyqtSignal()
class MyApp(QtWidgets.QMainWindow):
def __init__(self):
QtWidgets.QMainWindow.__init__(self)
# =============================================================================
# Create thread,worker, lock and move worker
# =============================================================================
self.thread=QtCore.QThread(self)
self.mot=motorpositioner()
self.mot.moveToThread(self.thread)
# =============================================================================
# Putting buttons and GUI stuff in place
# =============================================================================
self.button=QtWidgets.QPushButton('Quit thread',self)
self.button2=QtWidgets.QPushButton('Test locking',self)
layout = QtWidgets.QHBoxLayout()
layout.addWidget(self.button)
layout.addWidget(self.button2)
self.button2.move(0,50)
self.setLayout(layout)
self.setGeometry( 300, 300, 350, 300 )
# =============================================================================
# Making and connecting signals
# =============================================================================
self.qthread_sig=expSignals()
self.thread.started.connect(self.mot.do_it)
self.button.clicked.connect(self.qthread_sig.pause.emit)
self.button2.clicked.connect(self.test_lock)
self.qthread_sig.pause.connect(self.thread.quit)
# =============================================================================
# Start Thread
# =============================================================================
self.thread.start()
# =============================================================================
# Mutex should lock print
# =============================================================================
def test_lock(self):
#global lock
with QtCore.QMutexLocker(lock):
print('Printed in main thread')
def closeEvent(self,event):
self.qthread_sig.pause.emit()
event.accept()
class motorpositioner(QtCore.QObject):
def __init__(self):
QtCore.QThread.__init__(self)
# =============================================================================
# Create timer and lock, connect timer to print
# =============================================================================
self.timer = QtCore.QTimer(parent=self)
self.timer.timeout.connect(self.query_mot)
self.stat=0
#QtCore.pyqtSlot()
def do_it(self):
# =============================================================================
# Start timer
# =============================================================================
self.timer.start(5000)
#QtCore.pyqtSlot()
def query_mot(self):
#global lock
with QtCore.QMutexLocker(lock):
print('Printed in worker thread')
time.sleep(5)
if __name__ == "__main__":
if not QtWidgets.QApplication.instance():
app = QtWidgets.QApplication(sys.argv)
else:
app = QtWidgets.QApplication.instance()
window = MyApp()
window.show()
app.exec_()
I don't actually want to lock the print function but rather make sure that only one thread at a time talks to a positioning platform controlled from the PC.

Related

wxPython threading - GUI freezes while doing an animation in GLCanvas

What is the proper way of refreshing a GLCanvas (with heavy 3D geometries) without freezing the GUI?
I'm trying to use a set of GUI elements (buttons, checkboxes) to control a GLCanvas which is refreshed to display a 3D animation. My problem is that the GUI elements are not responsive when the animation loop is on.
I've tried to launch the animation loop in three different ways:
Option 1: within a thread
Option 2: using wx.lib.delayedresult (most likely similar to thread)
Option 3: calling Refresh after the onDraw event
It seems I can get the Option 1 to work, but I need to introduce a sleep delay with time.sleep(xxx) in between the call to the Refresh of the canvas. Otherwise the GUI remains poorly responsive: resizing the window, will end up "graying" the GUI elements, clicking on the checkboxes will trigger events but not the "checking" of the box, the "mouseover" effect over buttons does not work, etc.
I enclose a small working example. In my actual application, I animate a 3D geometry using moderngl, and I noticed that the "sleep" time required varies depending on how heavy the geometry is (as opposed to this example which is extremely lightweight and works with a delay as small as 0.00001s). I'm wondering what I'm missing. Thanks!
import wx
from wx import glcanvas
import wx.lib.delayedresult as delayedresult
from OpenGL.GL import *
import OpenGL.GL.shaders
import time
import numpy as np
from threading import Thread
# --- Option 1: Thread
class TestThread(Thread):
def __init__(self, parent, canvas):
Thread.__init__(self)
self.parent=parent
self.canvas=canvas
self.start() # start the thread
def run(self):
print('Thread running... ')
while self.canvas.animate:
#time.sleep(0.01) # <<<<<<<<<<<< This line needed
self.canvas.Refresh()
print('Tread done ')
class OpenGLCanvas(glcanvas.GLCanvas):
def __init__(self, parent):
glcanvas.GLCanvas.__init__(self, parent, -1, size=(400,400))
self.context = glcanvas.GLContext(self)
self.SetCurrent(self.context)
self.init = False
self.animate = False
self.refreshAfter = False
self.t=0
self.Bind(wx.EVT_PAINT, self.OnPaint)
def OnPaint(self, event):
wx.PaintDC(self)
if not self.init:
self.InitGL()
self.init = True
self.OnDraw()
def InitGL(self):
glEnable(GL_DEPTH_TEST)
def OnDraw(self):
""" Called at every frame"""
glClearColor(0.1, 0.0, np.mod(self.t,1), 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
if self.animate:
self.t+=0.0005 # increment time
# ---- Option 3
if self.refreshAfter:
self.Refresh() # Trigger next frame
self.SwapBuffers()
# --- Option 2: delayed results
def onAnimDelayedEnd(self, thread):
""" Consumer """
print('Delayed result done')
jobID = thread.getJobID()
result = thread.get()
def onAnimDelayedStart(self):
print('Delayed result running... ')
while self.animate:
self.Refresh()
class MyPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.canvas = OpenGLCanvas(self)
# GUI
self.rot_btn = wx.Button(self, -1, label="Toggle animation", pos=(430, 10))
self.cbDo = wx.CheckBox (self, -1, label="Do something", pos=(430 ,100))
self.radDo = wx.RadioButton(self, -1, label="Do something", pos=(430,140))
self.radDo2= wx.RadioButton(self, -1, label="Do something", pos=(430,180))
# Binding
self.rot_btn.Bind(wx.EVT_BUTTON, self.toggleAnim)
self.radDo.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.radDo2.Bind(wx.EVT_RADIOBUTTON, self.doSomething)
self.cbDo.Bind(wx.EVT_CHECKBOX , self.doSomething)
def toggleAnim(self, event):
if not self.canvas.animate:
self.canvas.animate = True
# --- Option 1: thread
TestThread(self, self.canvas)
# --- Option 2: delayed result
#delayedresult.startWorker(self.canvas.onAnimDelayedEnd, self.canvas.onAnimDelayedStart, jobID=1)
# --- Option 3: refreshloop
#self.canvas.refreshAfter=True
#self.canvas.Refresh() # set the canvas into an "infinite" refresh loop
else:
self.canvas.animate = False
def doSomething(self, event):
print('Do something')
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="My wx frame", size=(600,400))
self.Bind(wx.EVT_CLOSE, self.on_close)
self.panel = MyPanel(self)
def on_close(self, event):
self.Destroy()
if __name__ == "__main__":
app = wx.App()
frame = MyFrame().Show()
app.MainLoop()

Is there a way to cancel a background worker task in PyQt5 using a pushButton?

I'm trying to make the background worker start with the start button and stop with the stop button. When I push start the program starts but I can't stop it and it also exits the gui when completed. Sorry, I am a novice just trying to learn. I found this example here and I am just trying to expand on it.
Background thread with QThread in PyQt
# main.py
from PyQt5.QtCore import QThread
from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QGridLayout, QPushButton
import sys
import worker
class Form(QWidget):
def __init__(self):
super().__init__()
self.label = QLabel("0") # start out at 0.
##### I added the stuff below 6/8/2020 ####################################
k = 2
self.button1 = QPushButton("Push to start") # create start button
self.button2 = QPushButton("Push to Stop") # create a stop button.
self.button1.clicked.connect(self.start) # connect button to function
self.button2.clicked.connect(self.stop) # connect button to function
########################################################################
# 1 - create Worker and Thread inside the Form
self.obj = worker.Worker() # no parent! #### Calls to worker.py
self.thread = QThread() # no parent! #### Creates an empty thread
# 2 - Connect Worker`s Signals to Form method slots to post data.
self.obj.intReady.connect(self.onIntReady) # connects the definition below
# 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(k)) #### starts the counter in worker.py
# 6 - Start the thread
self.thread.start()
# 7 - Start the form
self.initUI()
def start(self):
k=2
self.obj.procCounter(k)
def stop(self):
self.thread.quit()
def initUI(self):
grid = QGridLayout()
self.setLayout(grid)
grid.addWidget(self.label,0,1)
grid.addWidget(self.button1, 0, 0)
grid.addWidget(self.button2, 0, 2)
self.move(300, 150)
self.setWindowTitle('thread test')
self.show()
def onIntReady(self, i):
self.label.setText("{}".format(i))
print(i)
app = QApplication(sys.argv)
form = Form()
sys.exit(app.exec_())
# worker.py
from PyQt5.QtCore import QThread, QObject, pyqtSignal, pyqtSlot
import time
class Worker(QObject): # this is called by Worker.worker() in main.py
finished = pyqtSignal() # sets up to emit a signal back to main.py
intReady = pyqtSignal(int) # emits an integer to main.py def onIntReady
#pyqtSlot()
def procCounter(self,k): # A slot takes no params
for i in range(1, 10):
time.sleep(1)
self.intReady.emit(i*k) # emits the signal to onIntReady
#def procCounter(self): # A slot takes no params
# for i in range(1, 100):
# time.sleep(1)
# self.intReady.emit(i)
self.finished.emit()

Pyqt threaded processes application crashing on second launch

I made a small test application to practice threading - on the first launch it works fine, with the loop printing to the console while the gui is still running. However, the kernel crashes and restarts if I close the app and then open it again afterwards.
I've had this issue before with pyqt5 applications, and it's usually solved when I add the QCoreApplication.instance() section at the end of my script. I was wondering whether this time there was an error with the way I implemented my threading.
import sys
import time
from PyQt5 import QtWidgets
from PyQt5.QtCore import QObject, QThread, pyqtSignal
from PyQt5.QtCore import QCoreApplication
class Worker(QObject):
finished = pyqtSignal()
def __init__(self):
super(Worker, self).__init__()
self.working = True
def work(self):
while self.working:
# do stuff here
print("I'm running")
time.sleep(1)
self.finished.emit() # alert our gui that the loop stopped
class Window(QtWidgets.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(50, 50, 200, 150)
self.setWindowTitle("Program")
self.startbtn = QtWidgets.QPushButton("Start", self)
self.startbtn.resize(self.startbtn.minimumSizeHint())
self.startbtn.move(50, 50)
self.stopbtn = QtWidgets.QPushButton("Stop", self)
self.stopbtn.move(50, 100)
self.stopbtn.resize(self.stopbtn.minimumSizeHint())
self.thread = QThread()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.startbtn.clicked.connect(self.thread.start) # start the thread when we click the start button
self.thread.started.connect(self.worker.work) # begin our worker object's loop
self.stopbtn.clicked.connect(self.stop_loop) # stop the loop on the stop button click
self.worker.finished.connect(self.loop_finished) # do something in the gui when the worker loop ends
def stop_loop(self):
self.worker.working = False
def loop_finished(self):
print('Looped Finished')
if __name__ == "__main__":
app = QCoreApplication.instance() # Fixes error with kernel crashing on second run of application
if app is None: # PyQt doesn't like multiple QApplications in the same process, therefore
app = QtWidgets.QApplication(sys.argv) # apart from the initial run, the first QApplication instance will run
window = Window()
window.show()
app.exec_()

PyQt signals between threads not emitted

I am stuck. It should be easy and I have done it many times using the C++ API of Qt however for some reason several of my signals/slots are not working when I'm doing this in PyQt (I've recently started with the concept of a worker QObject in PyQt). I believe it has to do something with the separate thread I'm emitting my signals to/from.
from PyQt4.QtCore import QThread, QObject, pyqtSignal, pyqtSlot, QTimer
from PyQt4.QtGui import QApplication, QWidget, QVBoxLayout, QPushButton, QLabel
class Slave(QObject):
countSignal = pyqtSignal(int)
def __init__(self, parent = None):
super(Slave, self).__init__()
self.toggleFlag = False
self.counter = 0
#pyqtSlot()
def work(self):
if not self.toggleFlag: return
if self.counter > 10: self.counter = 0
self.counter += self.counter
self.countSignal.emit(self.counter)
#pyqtSlot()
def toggle(self):
self.toggleFlag = not self.toggleFlag
class Master(QWidget):
toggleSignal = pyqtSignal()
def __init__(self, parent = None):
super(Master, self).__init__()
self.initUi()
self.setupConn()
def __del__(self):
self.thread.quit()
while not self.thread.isFinished(): pass
def initUi(self):
layout = QVBoxLayout()
self.buttonToggleSlave = QPushButton('Start')
self.labelCounterSlave = QLabel('0')
layout.addWidget(self.buttonToggleSlave)
layout.addWidget(self.labelCounterSlave)
self.setLayout(layout)
self.show()
def setupConn(self):
self.thread = QThread()
slave = Slave()
timer = QTimer()
timer.setInterval(100)
# Make sure that both objects are removed properly once the thread is terminated
self.thread.finished.connect(timer.deleteLater)
self.thread.finished.connect(slave.deleteLater)
# Connect the button to the toggle slot of this widget
self.buttonToggleSlave.clicked.connect(self.toggle)
# Connect widget's toggle signal (emitted from inside widget's toggle slot) to slave's toggle slot
self.toggleSignal.connect(slave.toggle)
# Connect timer's timeout signal to slave's work slot
timer.timeout.connect(slave.work)
timer.timeout.connect(self.timeout)
# Connect slave's countSignal signal to widget's viewCounter slot
slave.countSignal.connect(self.viewCounter)
# Start timer
timer.start()
# Move timer and slave to thread
timer.moveToThread(self.thread)
slave.moveToThread(self.thread)
# Start thread
self.thread.start()
#pyqtSlot(int)
def viewCounter(self, value):
print(value)
self.labelCounterSlave.setText(str(value))
#pyqtSlot()
def toggle(self):
print("Toggle called")
self.buttonToggleSlave.setText("Halt" if (self.buttonToggleSlave.text() == "Start") else "Start")
self.toggleSignal.emit()
#pyqtSlot()
def timeout(self):
print("Tick")
if __name__ == "__main__":
app = QApplication([])
w = Master()
w.setStyleSheet('cleanlooks')
app.exec_()
Following things are not triggered/emitted:
timeout() slot of my widget - I added this to see why the timer is not triggering my worker's slot but all I found out is that it doesn't work here either...
work() and toggle() slots inside my Slave worker class
countSignal - it is never emitted since my widget's viewCounter() slot is never triggered
I have no idea what I'm doing wrong. I have connected the signals and slots, started my timer, moved it along with the worker to my separate thread and started the thread.
Am I missing something here?
There are several issues with the code that are preventing it from working correctly.
As per the documentation, you must start (and stop) a timer from the thread it resides in. You cannot start it from another thread. If you want to have the timer reside in the thread, you should relocate the instantiation code to the Slave object and call timer.start() in a slot connected to the threads started signal. You do need to be careful here though as the Slave.__init__ method is going to still run in the main thread. Alternatively, you could just leave the timer in the main thread.
slave and timer are being garbage collected when setupConn() has finished. Store them as self.slave and self.timer. (Alternatively you should be able to specify a parent for them, but this seems to result in app crashes on exit so it's probably best to stick to storing them as instance attributes).
I assume the line self.counter += self.counter should really be self.counter += 1? Otherwise the counter is never incremented :)

How to write a multi-threaded Pyside application

What is the simplest way to multi-thread a Pyside application, so the GUI can be operational and the thread will still run?
Thread class:
class MyLongThread(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
while 1:
self.msleep(100)
print("run")
Complete .pyw
import sys,time
from PySide.QtGui import *
from PySide.QtCore import *
from PySide.QtWebKit import *
def thread():
global threade
threade = MyLongThread()
threade.run()
def thread_terminate():
global threade
threade.terminate()
class MyLongThread(QThread):
def __init__(self, parent = None):
QThread.__init__(self, parent)
self.exiting = False
def run(self):
while 1:
self.msleep(100)
print("run")
app = QApplication(sys.argv)
wid = QWidget()
wid.resize(250, 400)
wid.setWindowTitle('Threaded Program')
#wid.setWindowIcon(QIcon('web.png'))
#### BUTTONS
btn = QPushButton('Stop', wid)
btn.setToolTip('Stop the thread.') ## Stop the thread
btn.resize(btn.sizeHint())
btn.move(147, 50)
btn.clicked.connect(thread_terminate)
qbtn = QPushButton('Start', wid)
qbtn.setToolTip('Start the thread.') ## End the Thread
qbtn.resize(btn.sizeHint())
qbtn.move(27, 50)
qbtn.clicked.connect(thread)
####
#### LABEL
label = QLabel('Start The Thread :)',wid)
label.resize(label.sizeHint())
label.move(28, 15)
####
wid.show()
sys.exit(app.exec_())
When I run the code and press the start button, it freezes the gui but prints run.
Don't directly call thread.run() as this executes the method in the main thread. Instead, call thread.start() which will launch a thread and start executing your run() method in the thread.

Resources