How to stop a QThread from the GUI - python

This is a follow up question to a previous one I posted earlier.
The problem is how to stop (terminate|quit|exit) a QThread from the GUI when using the recommended method of NOT subclassing Qthread, but rather vreating a QObject and then moving it to a QThread. Below if a working example. I can start the GUI and the Qthread and I can have the latter update the GUI. However, I cannot stop it. I tried several methods for qthread (quit(), exit(), and even terminate()) to no avail.
Help greatly appreciated.
Here is the complete code:
import time, sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *
class SimulRunner(QObject):
'Object managing the simulation'
stepIncreased = pyqtSignal(int, name = 'stepIncreased')
def __init__(self):
super(SimulRunner, self).__init__()
self._step = 0
self._isRunning = True
self._maxSteps = 20
def longRunning(self):
while self._step < self._maxSteps and self._isRunning == True:
self._step += 1
self.stepIncreased.emit(self._step)
time.sleep(0.1)
def stop(self):
self._isRunning = False
class SimulationUi(QDialog):
'PyQt interface'
def __init__(self):
super(SimulationUi, self).__init__()
self.goButton = QPushButton('Go')
self.stopButton = QPushButton('Stop')
self.currentStep = QSpinBox()
self.layout = QHBoxLayout()
self.layout.addWidget(self.goButton)
self.layout.addWidget(self.stopButton)
self.layout.addWidget(self.currentStep)
self.setLayout(self.layout)
self.simulRunner = SimulRunner()
self.simulThread = QThread()
self.simulRunner.moveToThread(self.simulThread)
self.simulRunner.stepIncreased.connect(self.currentStep.setValue)
self.stopButton.clicked.connect(simulThread.qui) # also tried exit() and terminate()
# also tried the following (didn't work)
# self.stopButton.clicked.connect(self.simulRunner.stop)
self.goButton.clicked.connect(self.simulThread.start)
self.simulThread.started.connect(self.simulRunner.longRunning)
self.simulRunner.stepIncreased.connect(self.current.step.setValue)
if __name__ == '__main__':
app = QApplication(sys.argv)
simul = SimulationUi()
simul.show()
sys.exit(app.exec_())

I know its long ago but i just stumbled over the same problem.
I have been also searching for an appropriate way to do this. Finally it was easy. When exiting the application the task needs to be stopped and the thread needs to be stopped calling its quit method. See stop_thread method on bottom. And you need to wait for the thread to finish. Otherwise you will get QThread: Destroyed while thread is still running' message at exit.
(I also changed my code to use pyside)
import time, sys
from PySide.QtCore import *
from PySide.QtGui import *
class Worker(QObject):
'Object managing the simulation'
stepIncreased = Signal(int)
def __init__(self):
super(Worker, self).__init__()
self._step = 0
self._isRunning = True
self._maxSteps = 20
def task(self):
if not self._isRunning:
self._isRunning = True
self._step = 0
while self._step < self._maxSteps and self._isRunning == True:
self._step += 1
self.stepIncreased.emit(self._step)
time.sleep(0.1)
print "finished..."
def stop(self):
self._isRunning = False
class SimulationUi(QDialog):
def __init__(self):
super(SimulationUi, self).__init__()
self.btnStart = QPushButton('Start')
self.btnStop = QPushButton('Stop')
self.currentStep = QSpinBox()
self.layout = QHBoxLayout()
self.layout.addWidget(self.btnStart)
self.layout.addWidget(self.btnStop)
self.layout.addWidget(self.currentStep)
self.setLayout(self.layout)
self.thread = QThread()
self.thread.start()
self.worker = Worker()
self.worker.moveToThread(self.thread)
self.worker.stepIncreased.connect(self.currentStep.setValue)
self.btnStop.clicked.connect(lambda: self.worker.stop())
self.btnStart.clicked.connect(self.worker.task)
self.finished.connect(self.stop_thread)
def stop_thread(self):
self.worker.stop()
self.thread.quit()
self.thread.wait()
if __name__ == '__main__':
app = QApplication(sys.argv)
simul = SimulationUi()
simul.show()
sys.exit(app.exec_())

I found out that my original question was actually two questions in one: in order to stop a secondary thread from the main one, you need two things:
Be able to communicate from the main thread down to the secondary thread
Send the proper signal to stop the thread
I haven't been able to solve (2), but I figured out how to solve (1), which gave me a workaround to my original problem. Instead of stopping the thread, I can stop the thread's processing (the longRunning() method)
The problem is that a a secondary thread can only respond to signals if it runs its own event loop. A regular Qthread (which is what my code used) does not. It is easy enough, though, to subclass QThread to that effect:
class MyThread(QThread):
def run(self):
self.exec_()
and used self.simulThread = MyThread() in my code instead of the original self.simulThread = Qthread().
This ensures that the secondary thread runs an event loop. That was not enough, though. The longRunning() method needs to have a chance to actually process the event coming down from the main thread. With the help of this SO answer I figured out that the simple addition of a QApplication.processEvent() in the longRunning() method gave the secondary thread such a chance. I can now stop the processing carried out in the secondary thread, even though I haven't figured out how to stop the thread itself.
To wrap up. My longRunning method now looks like this:
def longRunning(self):
while self._step < self._maxSteps and self._isRunning == True:
self._step += 1
self.stepIncreased.emit(self._step)
time.sleep(0.1)
QApplication.processEvents()
and my GUI thread has these three lines that do the job (in addition to the QThread subclass listed above):
self.simulThread = MyThread()
self.simulRunner.moveToThread(self.simulThread)
self.stopButton.clicked.connect(self.simulRunner.stop)
Comments are welcome!

You can stop the thread by calling exit() or quit() . In extreme cases, you may want to forcibly terminate() an executing thread. However, doing so is dangerous and discouraged. Please read the documentation for terminate() and setTerminationEnabled() for detailed information.
src: https://doc.qt.io/qtforpython/PySide2/QtCore/QThread.html

Related

QThread not work correctly and pyqt gui application freeze

I'm writing python gui application that process a image and send image color from serial port and show the results but unfortunately my gui freeze. i try using QApllication.processEvents and it is work but my program speed slow and speed is so important to me and every one second one iteration should be complete And then i use QThread and my application still freeze. Here is my code:
class Worker(QObject):
progress = pyqtSignal(int)
gui_update = pyqtSignal()
def __init__(self, knot, lay, baft):
super().__init__()
self.knot = knot
self.lay = lay
self.baft = baft
def run(self):
while self.knot <= knotter_width:
color_to_send = []
for i in range(1, number_of_knotter + 1):
color_to_send.append(self.baft["knotters"][i][self.lay][self.knot])
self.progress.emit(self.knot)
self.gui_update.emit() # for updating gui but not work
QThread setups:
self.thrd = QThread()
self.worker = Worker(self.knot, self.lay, self.baft)
self.worker.moveToThread(self.thrd)
self.thrd.started.connect(self.worker.run)
self.worker.progress.connect(self.progress)
self.worker.gui_update.connect(self.knotters_status)
self.worker.finish.connect(self.finished)
self.worker.ex.connect(self.thrd.quit)
self.worker.ex.connect(self.worker.deleteLater)
self.thrd.finished.connect(self.thrd.deleteLater)
self.thrd.start()
After three days of research i found that after setting up thread you need to call processEvents() just after creating thread object and in that situation it doesn't cause slowing down and every thing work perfectly.
note: you should put the parameters in init function.
this is my final code:
class Thrd(QThread):
progress = pyqtSignal(int)
gui_update = pyqtSignal()
finish = pyqtSignal(bool)
ex = pyqtSignal()
def __init__(self, knot, lay, baft):
super().__init__()
self.knot = knot
self.lay = lay
self.baft = baft
def run(self):
while condition:
# some process
self.progress.emit(self.knot)
self.gui_update.emit()
time.sleep(0.05) # give a little time to update gui
self.finish.emit(True)
self.ex.emit()
And setting up part:
self.thrd = Thrd(self.knot, self.lay, self.baft)
app.processEvents()
self.thrd.progress.connect(self.progress)
self.thrd.gui_update.connect(self.knotters_status)
self.thrd.finish.connect(self.finished)
self.thrd.ex.connect(self.thrd.quit)
self.thrd.finished.connect(self.thrd.deleteLater)
self.thrd.finished.connect(self.stop)
self.thrd.start()

PyQT5 Multi Thread Issue

I am trying to run Multithread using PYQT5 & Qthread
I have two pushbutton associated to threads (progressbar & one action waiting for 1 sec and then print "done") that are working perfectly as they are declared within the same Class.
I do have a third PushButton that I link to an action inserted within another Class. This one makes my program crash wihtout any log message. What exactly makes the program crash is the line "self.thread2.start()".
I do not understand why this is not working! can you help me undertsand the issue?
Thanks in advance
import sys
from PyQt5.QtCore import *
from thread_progressbar import *
from Test_MT import *
class SimulationUi(QtWidgets.QDialog):
def __init__(self):
super(SimulationUi, self).__init__()
self.btnStart = QtWidgets.QPushButton('Start')
self.btnStart2 = QtWidgets.QPushButton('Start')
self.btnStop = QtWidgets.QPushButton('Stop')
self.btnStop2 = QtWidgets.QPushButton('Stop')
self.btnQuit = QtWidgets.QPushButton('Quit')
self.myprogressbar = QtWidgets.QProgressBar()
self.myprogressbar2 = QtWidgets.QProgressBar()
self.grid = QtWidgets.QGridLayout()
self.grid.setSpacing(10)
self.grid.addWidget(self.btnStart,1,0)
self.grid.addWidget(self.btnStop,1,1)
self.grid.addWidget(self.myprogressbar,2,0,1,3)
self.grid.addWidget(self.btnStart2, 3, 0)
self.grid.addWidget(self.btnStop2, 3, 1)
self.setLayout(self.grid)
# ------------------------
#MULTI-THREAD MANAGEMENT
#------------------------
self.thread = QThread()
self.thread.start()
self.worker = thread_progressbar()
self.worker.moveToThread(self.thread)
self.worker.setValue.connect(self.myprogressbar.setValue)
self.btnStart.clicked.connect(self.worker.startpgbar)
self.btnStop.clicked.connect(lambda: self.worker.stoppgbar())
class_tst = MakeList()
class_tst.define_thread(self.btnStart2)
self.thread3 = QThread()
self.thread3.start()
self.worker3 = Test()
self.worker3.moveToThread( self.thread3 )
self.btnStop2.clicked.connect( self.worker3.MT )
def stop_thread(self):
self.worker.stop()
self.thread.quit()
self.thread.wait()
def quit_application(self):
self.close()
class MakeList():
def __init__(self):
super(MakeList, self).__init__()
def define_thread(self, MyObject):
self.thread2 = QThread()
self.thread2.start()
self.worker2 = Test()
self.worker2.moveToThread(self.thread2 )
MyObject.clicked.connect( self.worker2.MT )
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
simul = SimulationUi()
simul.show()
sys.exit(app.exec_())
Test_MT file
import os, time
from PyQt5 import QtCore
class Test(QtCore.QObject):
def MT(self):
time.sleep(1)
print("done")
Since the only reference to the MakeList instance in SimulationUi.__init__, is the local variable class_tst, this object together with it's attributes will be garbage collected when SimulationUi.__init__ returns. Since one of its attributes is a running thread (class_tst.thread2), this causes the program to crash. Easiest way around this is to make a persistent reference the MakeList object by assigning it to an instance variable of SimulationUi rather than to a local variable (i.e. use self.class_tst = MakeList() instead of class_tst=MakeList() in SimulationUi.__init__).

Python: Does PubSub and WxPython with Threading require wx.CallAfter?

I am using:
wxPython 4.0.7.post2
Pypubsub 4.0.3
Python 3.8.1
I have the following example program I have written:
import wx
import time
from threading import Thread
from pubsub import pub
TIME_UPDATED = "time.updated"
class MyFrame(wx.Frame):
def __init__(self):
super().__init__(parent=None, title="Example")
self.text = wx.StaticText(self, label="I will display seconds elapsed!")
self.othertext = wx.StaticText(self, label="I will Update")
sizer = wx.BoxSizer(orient=wx.VERTICAL)
sizer.Add(self.text)
sizer.Add(self.othertext)
self.SetSizer(sizer)
self.timer = wx.Timer(self)
pub.subscribe(self.UpdateTime, TIME_UPDATED)
self.Bind(wx.EVT_TIMER, self.OnTime, self.timer)
self.Show()
self.i = 0
self.timer.Start(500)
def OnTime(self, _):
self.i += 1
self.othertext.SetLabel(str(self.i))
def UpdateTime(self, seconds):
self.text.SetLabel("{seconds} seconds have elapsed".format(seconds=seconds))
self.text.Refresh()
class BackgroundThread(Thread):
def run(self):
time_elapsed = 0
while True:
# Lets sleep 1 second
time.sleep(1)
time_elapsed += 1
# <<<<---- This line is what I am worried about.
pub.sendMessage(TIME_UPDATED, seconds=time_elapsed)
if __name__ == '__main__':
app = wx.App()
frame = MyFrame()
background = BackgroundThread(daemon=True)
background.start()
app.MainLoop()
I am performing a pub.sendMessage(TIME_UPDATED, seconds=time_elapsed) without a wx.CallAfter and it seems to be working fine. I am not sure why.
Could someone please explain if wx.CallAfter is necessary anymore?
If it is can you explain why that is? Is it that some wx methods put something onto the dispatch queue whereas others do not?
Yes, you should still ensure that UI operations occur on the UI thread. Just because something is not safe to do does not mean that it doesn't happen to work okay (or apprear to work okay) in some cases.

Python,Gtk3: How to make the Progress bar pulsing while other stuffs are running

Based on Classes, i have window which contain a button and progressbar, whenever the button is clicked there two things should happen :
1 - should entried value from dialog pass to class ABCD
2 - While our class ABCD() do his stuff, should our progressbar do regular pulsing untill the class ABCD() finish process.
So the problem is that the progressbar pulse only one time,then stucked there till the class ABCD() finished, then its start pulsing regulary later.
Here is my try:
import gi,time
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GObject
class DialogExample(Gtk.Dialog):
def __init__(self, parent):
Gtk.Dialog.__init__(self, "My Dialog", parent, 0,
(Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
Gtk.STOCK_OK, Gtk.ResponseType.OK))
self.set_default_size(150, 100)
self.Myinput = Gtk.Entry()
box = self.get_content_area()
box.add(self.Myinput)
self.show_all()
class DialogWindow(Gtk.Window):
def __init__(self):
Gtk.Window.__init__(self, title="Dialog Example")
self.set_border_width(6)
Hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
self.add(Hbox)
self.button = Gtk.Button("Open dialog")
self.button.connect("clicked", self.on_button_clicked)
Hbox.pack_start(self.button, True, True, 0)
self.progressbar = Gtk.ProgressBar()
Hbox.pack_start(self.progressbar, True, True, 0)
#~~~~~~ Progress Bar
def on_timeout(self, user_data):
"""
Update value on the progress bar
"""
if self.activity_mode:
self.progressbar.pulse()
else:
new_value = self.progressbar.get_fraction() + 0.01
if new_value > 1:
new_value = 0
self.progressbar.set_fraction(new_value)
# As this is a timeout function, return True so that it
# continues to get called
return True
def on_button_clicked(self, widget):
dialog = DialogExample(self)
response = dialog.run()
if response == Gtk.ResponseType.OK:
variable = dialog.Myinput.get_text()
print("start")
dialog.destroy()
#ProgressBar time function
self.timeout_id = GObject.timeout_add(50, self.on_timeout, None)
self.activity_mode = False
self.progressbar.pulse()
#this for Updating the Windows and make the progressbar pulsing while waiting
# the class ABCD finish his stuff, finally should stop pulsing.
while Gtk.events_pending():
Gtk.main_iteration_do(False)
passing_instance = ABCD(variable)
class ABCD(object):
def __init__(self,value_of_dialog):
self.get_value = value_of_dialog
self.for_add = "______ add was done"
self.final_value = self.get_value+self.for_add
time.sleep(10)
print("gonna be finished")
print(self.final_value)
win = DialogWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()
As we can see here i already try to make pulse and refresh the windows in this part of code
self.timeout_id = GObject.timeout_add(50, self.on_timeout, None)
self.activity_mode = False
self.progressbar.pulse()
#this for Updating the Windows and make the progressbar pulsing while waiting
# the class ABCD finish his stuff, finally should stop pulsing.
while Gtk.events_pending():
Gtk.main_iteration_do(False)
Otherwise because in my class ABCD() i have time.sleep(10) should
the progress bar pulse only for that time 10 seconds later only then
stop.
How should this code gonna be, i need someone provide me the correct code, with little explain.
The issue with using sleep in order to emulate the passing of time is that sleep will stop everything that is happening in the thread which in this case prevents the thread to reach Gtk.main() which is needed to make your progressbar pulse or update.
So in order to do this properly there are 2 options:
Run ABCD in a separate thread such that the main thread can reach Gtk.main(). Which than will make sure that the progressbar moves as expected. A quick example of this looks like this:
self.abcd_thread = ABCD(variable)
self.abcd_thread.start()
class ABCD(Thread):
def __init__(self, value_of_dialog):
super(ABCD, self).__init__()
self.get_value = value_of_dialog
self.for_add = "______ add was done"
self.final_value = self.get_value+self.for_add
def run(self):
print "Starting " + self.name
time.sleep(10)
print("gonna be finished")
print(self.final_value)
print "Exiting " + self.name
When using this you can use self.abcd_thread.isAlive() to see whether the thread is still computing things. The way to return information heavily depends on the job placed in the thread.
Replace the time.sleep with the following fragment:
now = time.time()
while time.time() - now < 10:
# Insert any code here
Gtk.main_iteration_do(False)
This will still emulate ABCD doing stuff for 10 seconds but because we call Gtk.main_iteration_do(False) in each iteration of the loop GTK is able to update the interface during the loop.
In general the second option is the easiest as it only involves making Gtk.main_iteration_do(False) call during whatever your doing. The first option on the other hand is more useful when dealing with complex computations where adding Gtk calls doesn't fit in easily.

wxpython - Running threads sequentially without blocking GUI

I've got a GUI script with all my wxPython code in it, and a separate testSequences module that has a bunch of tasks that I run based on input from the GUI. The tasks take a long time to complete (from 20 seconds to 3 minutes), so I want to thread them, otherwise the GUI locks up while they're running. I also need them to run one after another, since they all use the same hardware. (My rationale behind threading is simply to prevent the GUI from locking up.) I'd like to have a "Running" message (with varying number of periods after it, i.e. "Running", "Running.", "Running..", etc.) so the user knows that progress is occurring, even though it isn't visible. I'd like this script to run the test sequences in separate threads, but sequentially, so that the second thread won't be created and run until the first is complete. Since this is kind of the opposite of the purpose of threads, I can't really find any information on how to do this... Any help would be greatly appreciated.
Thanks in advance!
gui.py
import testSequences
from threading import Thread
#wxPython code for setting everything up here...
for j in range(5):
testThread = Thread(target=testSequences.test1)
testThread.start()
while testThread.isAlive():
#wait until the previous thread is complete
time.sleep(0.5)
i = (i+1) % 4
self.status.SetStatusText("Running"+'.'*i)
testSequences.py
import time
def test1():
for i in range(10):
print i
time.sleep(1)
(Obviously this isn't the actual test code, but the idea is the same.)
You cannot wait in the GUI-thread with a while loop because you block the processing of the event-queue. One solution is to poll the state of the thread with a timer:
import wx
import time
from threading import Thread
def test1():
for i in range(10):
print i
time.sleep(1)
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, "Test")
panel = wx.Panel(self, -1)
sizer = wx.BoxSizer(wx.VERTICAL)
panel.SetSizer(sizer)
self.button = wx.Button(panel, 0, "Start")
sizer.Add(self.button, 0, wx.ALIGN_LEFT)
self.button.Bind(wx.EVT_BUTTON, self.OnButton)
self.text = wx.StaticText(panel, 0, "No test is running")
sizer.Add(self.text, 0, wx.ALIGN_LEFT)
self.timer = wx.Timer(self)
def OnButton(self, event):
self.testThread = Thread(target=test1)
self.testThread.start()
self.text.SetLabel("Running")
self.button.Disable()
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(20, oneShot=True)
event.Skip()
def PollThread(self, event):
if self.testThread.isAlive():
self.Bind(wx.EVT_TIMER, self.PollThread)
self.timer.Start(200, oneShot=True)
self.text.SetLabel(self.text.GetLabel() + ".")
else:
self.button.Enable()
self.text.SetLabel("Test completed")
app = wx.PySimpleApp()
TestFrame().Show()
app.MainLoop()
Figured out a way to do this. Instead of creating threads in my gui.py, I created a class that inherits from Thread, and runs all the tests in that class, then posts wxPython events when one test is done (so I can update the status bar) and when all tests are done (so I can inform the user that all tests are complete.
myEVT_TESTDONE = wx.NewEventType()
EVT_TESTDONE = wx.PyEventBinder(myEVT_TESTDONE , 1)
myEVT_ALLDONE = wx.NewEventType()
EVT_ALLDONE = wx.PyEventBinder(myEVT_ALLDONE, 1)
class TestDone(wx.PyCommandEvent):
def __init__(self, etype, eid, val=None):
wx.PyCommandEvent.__init__(self, etype, eid)
self._val = val
def GetValue(self):
return self._val
class AllDone(wx.PyCommandEvent):
def __init__(self, etype, eid):
wx.PyCommandEvent.__init__(self, etype, eid)
class TestSequence(Thread):
def __init__(self, parent, queue):
Thread.__init__(self)
self._queue = queue
self._parent = parent
self.start()
def run(self):
testCount = 0
for test in self._queue:
#Time-intensive task goes here
for i in range(10):
print i
sleep(1)
evt = TestDone(myEVT_TESTDONE, -1, i)
wx.PostEvent(self._parent, evt)
evt = AllDone(myEVT_ALLDONE, -1)
wx.PostEvent(self._parent, evt)
class MainSequence(wx.Frame):
def __init__(self, parent, id, title):
self.Bind(EVT_TESTDONE, self.testDoneEvt)
self.Bind(EVT_ALLDONE, self.allDoneEvt)
#...the rest of the wxPython code
def testDoneEvt(self, event):
#Set what to be done after every test, e.g. update progress bar
step = event.GetValue()
def allDoneEvt(self, event):
#Set what to be done after all tests, e.g. display "Tests complete"
program = wx.App()
window = MainSequence(None, -1, 'App title')
program.MainLoop()

Categories

Resources