how to interrupt a thread - multithreading

i want to kill a thread "mid run" i have 2 loops one dose some things (lest call it 1) while the other (lets call it 2) listens to a keyboard event and i want 1 to terminate as soon as 2 detects the event
i am sorry that my code is a mess it can probably be ignored there is not to much i was able to do
import threading
import keyboard
import time
r = sr.Recognizer()
class Main(threading.Thread):
def __init__(self):
super(Main, self).__init__()
self._stop_event = threading.Event()
def stop(self):
self._stop_event.set()
def stopped(self):
return self._stop_event.is_set()
def run(self):
main()
class Listener(threading.Thread):
def run(self):
listener()
def listener():
time.sleep(0.3)
keyboard.wait('alt + s')
print('stop')
_main.stop()
time.sleep(0.3)
keyboard.wait('alt + s')
print('start')
_main.start()
def main():
_listener.start()
while 1:
print('m')
time.sleep(1)
if __name__ == '__main__':
_main = Main()
_listener = Listener()
keyboard.wait('alt + s')
_main.start()
keyboard.wait('esc')

Related

How to communicate QThread with my Widgets from main class

My app is freezing because i need to create a new QThread, but am a little confused how to create it when i call widgets that exists on the main class.
here is my code so far ...
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.uic import loadUi
import time
##Main Class
class HWindow(QtWidgets.QWidget):
def __init__(self):
super(HWindow, self).__init__()
loadUi("Hw.ui",self) ##load ui
#Create a QThread object
self.thread = QtCore.QThread()
#Create a QThread object
self.workerMain = WorkerMain()
#Move worker to the thread
self.workerMain.moveToThread(self.thread)
#Start the thread
self.thread.start()
self.runningMain = False
def activateMain(self):
if self.chkb_main.isChecked():
self.lbl_disena_main.setText("Activated")
self.lbl_disena_main.setStyleSheet('color: green;')
#Change variable and call the function
runningMain = True
self.myFunction()
else:
self.lbl_disena_main.setText("Disabled")
self.lbl_disena_main.setStyleSheet('color: red;')
#Change variable and call the function
runningMain = False
def myFunction(self):
while self.runningMain == True:
if self.gpb_main.isChecked() and self.chkb_main.isChecked():
print("running ...")
time.sleep(3)
##Worker Class
class WorkerMain(QtCore.QObject):
threadRunning = QtCore.pyqtSignal()
def __init__(self):
super(WorkerMain, self).__init__()
def run(self):
print("Thread Running ...")
#i cant call my widgets from my main class from here.
'''
while self.runningMain == True:
if self.gpb_main.isChecked() and self.chkb_main.isChecked():
print("running ...")
time.sleep(3)
'''
def stop(self):
print("Thread Stopped ...")
self.terminate()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
hWindow = QtWidgets.QWidget()
hWindow = HWindow()
hWindow.show()
sys.exit(app.exec_())
Based on pyqt5 documentation and examples, you create a loop on the run method, but it is complicated when i have to create that loop based on what the user select (GUI widgets).
i just figure it out, here is my code ...
class HWindow(QtWidgets.QWidget):
def __init__(self):
super(HWindow, self).__init__()
loadUi("Hw.ui",self) ##load ui
#Create a QThread object
self.thread = QtCore.QThread()
#Create a QThread object
self.workerMain = WorkerMain(self)
#Move worker to the thread
self.workerMain.moveToThread(self.thread)
#Connect the start signal to the run slot
self.workerMain.threadRunning.connect(self.workerMain.run)
#Connect the activateMain signal to the start slot
self.chkb_main.toggled.connect(self.activateMain)
#Start the thread
self.thread.start()
def activateMain(self):
if self.chkb_main.isChecked():
self.lbl_disena_main.setText("Activated")
self.lbl_disena_main.setStyleSheet('color: green;')
self.workerMain.runningMain = True
self.workerMain.threadRunning.emit()
if not self.thread.isRunning():
self.thread.start()
else:
self.lbl_disena_main.setText("Disabled")
self.lbl_disena_main.setStyleSheet('color: red;')
self.workerMain.runningMain = False
self.workerMain.stop()
##Worker Class
class WorkerMain(QtCore.QObject):
threadRunning = QtCore.pyqtSignal()
def __init__(self, parent):
super(WorkerMain, self).__init__()
self.parent = parent
self.runningMain = False
def run(self):
print("Thread Running ...")
while self.runningMain:
if self.parent.gpb_main.isChecked() and self.parent.chkb_main.isChecked():
print("running ...")
time.sleep(3)
def stop(self):
print("Thread Stopped ...")
if self.parent.thread.isRunning():
self.parent.thread.quit()
self.parent.thread.wait()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
hWindow = QtWidgets.QWidget()
hWindow = HWindow()
hWindow.show()
sys.exit(app.exec_())

DelphiVCL for Python threading

Using DelphiVCL for Python it seems to be blocking threads.
Testing the code (below) I would expect it to output / update timing from the two - 2 - threads. However the VCL app seems to be blocking the other threads.
How can this be fixed to allow running background threads with VCL?
from delphivcl import *
import threading
from time import sleep, strftime as now
from datetime import datetime
FORMAT = '%Y-%m-%d %H:%M:%S'
class Thread(threading.Thread):
def __init__(self, parent, threadname):
super(Thread, self).__init__()
self._stop = threading.Event()
self.parent = parent
self.name = threadname
def run(self):
while not self._stop.is_set():
text = "{0} {1} {2}".format(now(FORMAT), self.name, str(self._stop.is_set()))
if self.parent:
self.parent.lblHello.Caption = text
else:
print(text, flush=True)
sleep(1)
print(now(FORMAT), self.name, str(self._stop.is_set()))
def stop(self):
self._stop.set()
class GUIApp(Form):
def __init__(self, owner):
self.OnDestroy = self.__form_ondestroy
self.lblHello = Label(self)
self.lblHello.SetProps(Parent=self)
self.thread = Thread(self, 'inside app')
self.thread.start()
self.lblTimer = Label(self)
self.lblTimer.SetProps(Parent=self)
self.lblTimer.Top = 16
self.timer = Timer(self)
self.timer.Interval = 1
self.timer.OnTimer = self.__form_timer
self.timer.Enabled = True
def __form_timer(self, sender):
self.lblTimer.Caption = datetime.utcnow().strftime(FORMAT + '.%f')[:-3]
def __form_ondestroy(self, sender):
self.thread.stop()
if __name__ == '__main__':
Application.Initialize()
Application.Title = "Hello Delphi VCL"
app = GUIApp(Application)
app.Show()
t2 = Thread(None, 'outside app')
t2.start()
FreeConsole()
Application.Run()
app.Destroy()
t2.stop()
EDIT: Adding a timer (code updated) to the VCL app starts the background threads.

Is there a way to send an event to wxApp?

I am using wxPython for the GUI, and I have a different thread that interacts with some I/O. Is there a way to update the GUI to show the I/O status when the thread receives an event from the I/O device?
To be clearer: every 3 seconds the IOthread asks to a parking bar if it is up or down, and I want the GUI to show a red led if it is down or a green led when it is up. So basically I need to communicate the information received by the IOthread to the GUI, which is running in the main thread
You could have a peek at pubsub but as you are using wxPython with a thread, it's easier to use a user defined event.
This adapted code, simply alternates between True and False, obviously you would change that and only post the event, if it changes.
from threading import Thread
import wx
import time
import wx.lib.newevent
progress_event, EVT_PROGRESS_EVENT = wx.lib.newevent.NewEvent()
class WorkThread(Thread):
def __init__(self,parent_target):
"""Init Worker Thread Class."""
Thread.__init__(self)
self.target = parent_target
self.stop_work_thread = 0
self.start() # start the thread
def run(self):
curr_loop = True
while True:
if self.stop_work_thread == 1: # Exit
break
time.sleep(1)
if self.stop_work_thread == 2: # Paused
continue
curr_loop = not curr_loop
evt = progress_event(active=curr_loop,name=self.name)
#Send back current bar position up True, Down False
try:
wx.PostEvent(self.target, evt)
except: # The parent frame has probably been destroyed
self.stop()
return
def stop(self):
self.stop_work_thread = 1
def pause(self):
if self.stop_work_thread == 2:
self.stop_work_thread = 0
self.target.pause_btn.SetLabel('Pause')
else:
self.stop_work_thread = 2
self.target.pause_btn.SetLabel('Paused')
class Progress(wx.Frame):
def __init__(self, parent, title):
super(Progress, self).__init__(parent, title = title,size = (500,300))
left_sizer = wx.BoxSizer(wx.VERTICAL)
middle_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.panel = wx.Panel(self)
self.start_btn = wx.Button(self.panel, label="Start")
self.stop_btn =wx.Button(self.panel, label="Stop")
self.pause_btn =wx.Button(self.panel, label="Pause")
self.quit_btn =wx.Button(self.panel, label="Quit")
self.logger = wx.TextCtrl(self.panel, size=(200,200), style=wx.TE_MULTILINE | wx.TE_READONLY)
self.stop_btn.Disable()
self.pause_btn.Disable()
left_sizer.Add(self.start_btn,0,wx.EXPAND)
left_sizer.Add(self.stop_btn,0,wx.EXPAND)
left_sizer.Add(self.pause_btn,0,wx.EXPAND)
left_sizer.Add(self.quit_btn,0,wx.EXPAND)
middle_sizer.Add(self.logger,0,wx.EXPAND)
self.mainsizer = wx.BoxSizer(wx.HORIZONTAL)
self.mainsizer.Add(left_sizer)
self.mainsizer.Add(middle_sizer)
self.panel.SetSizer(self.mainsizer)
self.Layout()
self.start_btn.Bind(wx.EVT_BUTTON, self.onStart)
self.stop_btn.Bind(wx.EVT_BUTTON, self.onCancel)
self.pause_btn.Bind(wx.EVT_BUTTON, self.onPause)
self.quit_btn.Bind(wx.EVT_BUTTON, self.onExit)
#Bind to the progress event issued by the thread
self.Bind(EVT_PROGRESS_EVENT, self.OnActive)
self.Bind(wx.EVT_CLOSE, self.onExit)
def OnActive(self, event):
if not self.work.is_alive():
return
active = event.active
ident = event.name
if active:
self.logger.Clear()
self.logger.SetBackgroundColour("green")
self.logger.write('\n\n\n\n\n\t\tBar is Up')
else:
self.logger.Clear()
self.logger.SetBackgroundColour("red")
self.logger.write('\n\n\n\n\n\t\tBar is Down')
def onStart(self, event):
self.start_btn.Disable()
self.work = WorkThread(parent_target=self)
self.pause_btn.Enable()
self.stop_btn.Enable()
def onPause(self, event):
if self.work.is_alive():
self.work.pause() # Pause the thread
def onCancel(self, event):
"""Cancel thread process"""
try:
self.work.stop()
self.work.join()
except:
pass
self.onFinish()
def onFinish(self):
"""thread process finished - clean up"""
self.start_btn.Enable()
self.stop_btn.Disable()
self.pause_btn.Disable()
self.pause_btn.SetLabel("Pause")
self.logger.Clear()
self.logger.SetBackgroundColour("white")
def onExit(self, event):
self.onCancel(None)
self.onFinish()
self.Destroy()
app = wx.App()
frame = Progress(None,'Bar Up/Down Display')
frame.Show()
app.MainLoop()

Catch final signal from QTimer object

I have a simple PyQt script. When I click a button, it starts a QTimer object and increments a progress bar. What I want is to change the label of my text when my progress bar reaches 100%. It worked for me once, but I can't get it to work anymore. What am I doing wrong?
Here's the main part of my code.
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QProgressBar demo')
self.timerButton = QPushButton("Start", self)
self.timerButton.clicked.connect(self.timerStart)
self.timerObject = QTimer(self)
#self.timerObject.destroyed.connect(lambda:self.timerButton.setText("Finished") )
self.timerObject.destroyed.connect(lambda:print("Called" ))
self.progressBar = QProgressBar(self)
self.progressBar.setGeometry(10, 20, 290, 25)
self.timerButton.move(110,150)
self.progressBar.move(10,100)
self.increment = 0
self.resize(300, 300)
self.show()
#pyqtSlot()
def headsUp(self):
if(self.increment >= 100):
self.timerObject.stop()
else:
self.increment += 1
self.progressBar.setValue(self.increment)
return
def timerStart(self):
if (self.timerObject.isActive()):
self.timerObject.stop()
self.timerButton.setText("Resume")
else:
self.timerObject.timeout.connect(self.headsUp)
self.timerButton.setText("Pause")
self.timerObject.start(100)
destroyed is only issued when you delete the object, that a QTimer peer does not imply that it is deleted from memory, therefore it does not emit that signal, a possible solution is to create a signal for the QProgressBar when the value takes the maximum value as shown below:
import sys
from PyQt5 import QtCore, QtWidgets
class ProgressBar(QtWidgets.QProgressBar):
finished = QtCore.pyqtSignal()
def __init__(self, *args, **kwargs):
super(ProgressBar, self).__init__(*args, *kwargs)
self.valueChanged.connect(self.on_valueChanged)
#QtCore.pyqtSlot(int)
def on_valueChanged(self, val):
if val == self.maximum():
self.finished.emit()
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QProgressBar demo')
self.timerButton = QtWidgets.QPushButton("Start", self)
self.timerButton.clicked.connect(self.timerStart)
self.timerObject = QtCore.QTimer(self)
self.progressBar = ProgressBar(self)
self.progressBar.finished.connect(lambda: print("Called" ))
self.increment = 0
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.progressBar)
lay.addWidget(self.timerButton)
self.resize(300, 300)
#QtCore.pyqtSlot()
def headsUp(self):
if self.increment >= 100:
self.timerObject.stop()
else:
self.increment += 1
self.progressBar.setValue(self.increment)
#QtCore.pyqtSlot()
def timerStart(self):
if self.timerObject.isActive():
self.timerObject.stop()
self.timerButton.setText("Resume")
else:
self.timerObject.timeout.connect(self.headsUp)
self.timerButton.setText("Pause")
self.timerObject.start(100)
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
Another best option is to use QTimeLine and its finished signal:
import sys
from PyQt5 import QtCore, QtWidgets
class MainWindow(QtWidgets.QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QProgressBar demo')
self.timerButton = QtWidgets.QPushButton("Start", self)
self.timerButton.clicked.connect(self.timerStart)
self.timerObject = QtCore.QTimeLine(1000, self)
self.timerObject.setFrameRange(0, 100)
self.progressBar = QtWidgets.QProgressBar(self)
self.timerObject.frameChanged.connect(self.progressBar.setValue)
self.timerObject.finished.connect(lambda: print("Called" ))
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.progressBar)
lay.addWidget(self.timerButton)
self.resize(300, 300)
#QtCore.pyqtSlot()
def timerStart(self):
if self.timerObject.state() == QtCore.QTimeLine.Running:
self.timerObject.stop()
self.timerButton.setText("Resume")
else:
self.timerButton.setText("Pause")
self.timerObject.resume()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
w = MainWindow()
w.show()
sys.exit(app.exec_())
This isn't working because you are connecting to the timers destoryed signal, but the timer is not being destroyed. To use this code as is, call self.timerObject.deleteLater() after you stop the timer.

QThread workaround in a Basic GUI

Here is the sample code
import sys, time
from PyQt4 import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.connect(self.testButton, QtCore.SIGNAL("released()"), self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
print "Add: ", text
self.listwidget.addItems(text)
self.listwidget.sortItems()
# def addBatch(self, text="test", iters=6, delay=0.3):
# for i in range(iters):
# time.sleep(delay)
# self.add(text + " " + str(i))
def test(self):
self.listwidget.clear()
#self.addBatch("_non_thread", iters=6, delay=0.3)
self.workThread = WorkThread()
self.connect(self.workThread, QtCore.SIGNAL("update(QString"), self.add)
self.workThread.start()
class WorkThread(QtCore.QThread):
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
self.wait()
def run(self):
for i in range(6):
time.sleep(0.3)
self.emit(QtCore.SIGNAL('update(QString'), "from work thread " + str(i))
return
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
app.exec_()
Here I have a basic GUI with a listwidget and a push button. When I press the push button, I program should wait a moment and display a string in that listwidget. The WorkThread class does the waiting stuff and after waiting it emits a signal. But when I run the program, only I can see the GUI and nothing is displayed in the listwidget.
Can somebody tell me what is the reason behind this and how to fix this ?
QListWidget.addItems expects a list of items but you're giving it a single QString. You should use .addItem.
There are also a few minor corrections. You don't need to implement __del__ in your thread. You can skip __init__ if you're not doing additional stuff. And you should use new style signals and connections.
Here is the result with all corrections:
import sys, time
from PyQt4 import QtCore, QtGui
class MyApp(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self, parent)
self.setGeometry(300, 300, 280, 600)
self.setWindowTitle('threads')
self.layout = QtGui.QVBoxLayout(self)
self.testButton = QtGui.QPushButton("test")
self.testButton.clicked.connect(self.test)
self.listwidget = QtGui.QListWidget(self)
self.layout.addWidget(self.testButton)
self.layout.addWidget(self.listwidget)
def add(self, text):
print "Add: ", type(text)
self.listwidget.addItem(text)
self.listwidget.sortItems()
# def addBatch(self, text="test", iters=6, delay=0.3):
# for i in range(iters):
# time.sleep(delay)
# self.add(text + " " + str(i))
def test(self):
self.listwidget.clear()
#self.addBatch("_non_thread", iters=6, delay=0.3)
self.workThread = WorkThread()
self.workThread.update.connect(self.add)
self.workThread.start()
class WorkThread(QtCore.QThread):
update = QtCore.pyqtSignal(str)
def run(self):
for i in range(6):
time.sleep(0.3)
self.update.emit("from work thread " + str(i))
app = QtGui.QApplication(sys.argv)
test = MyApp()
test.show()
sys.exit(app.exec_())
The PyQtWiki contains a pretty elaborate example how to send signals from a background thread to the UI. Apparently, no special magic is necessary as long as you QThread to implement your thread.
But I noticed that you use the signal released() to connect the button with the method test(). Did you try clicked()?

Resources