pyqt: How to quit a thread properly - python

I wrote an pyqt gui and used threading to run code which needs a long time to be executed, but I want to have the choice to stop the execution safely. I dont want to use the get_thread.terminate() method. I want to stop the code by a special function (maybe del()). My problem is that, I wrote the code in a own class and just want to abort the class without changing a lot of syntax.
Edit: It was mentioned that one has to pass a flag to the class, which has to be checked constantly. How do I send this flag to the class? Because the flag has to change the value, when one presses the stop button.
Edit 2: My solution so far is, to declerate a global variable with the name running_global. I changed self.get_thread.terminate() to running_global = False and I check constantly in my long_running_prog if the variable has been set False. I think this solution is ugly, so I would be pretty happy if someone has a better idea.
This is my code for the dialog where I start the thread:
class SomeDialog(QtGui.QDialog,
userinterface_status.Ui_status_window):
finished = QtCore.pyqtSignal(bool)
def __init__(self):
"""
:param raster: Coordinates which are going to be scanned.
"""
super(self.__class__, self).__init__() # old version, used in python 2.
self.setupUi(self) # It sets up layout and widgets that are defined
self.get_thread = SomeThread()
# Conencting the buttons
self.start_button.clicked.connect(self.start)
self.stop_button.clicked.connect(self.stop)
self.close_button.clicked.connect(self.return_main)
# Connecting other signals
self.connect(self.get_thread, QtCore.SIGNAL("stop()"), self.stop)
self.connect(self.get_thread, QtCore.SIGNAL("update_status_bar()"), self.update_status_bar)
def return_main(self):
"""
Function is excecuted, when close button is clicked.
"""
print("return main")
self.get_thread.terminate()
self.close()
def start(self):
"""
Starts the thread, which means that the run method of the thread is started.
"""
self.start_button.setEnabled(False)
self.get_thread.start()
def stop(self):
print("Stop programm.")
self.start_button.setEnabled(True)
self.get_thread.quit()
def end(self):
QtGui.QMessageBox.information(self, "Done!", "Programm finished")
def closeEvent(self, event):
"""
This method is called, when the window is closed and will send a signal to the main window to activaete the
window again.
:param event:
"""
self.finished.emit(True)
# close window
event.accept()
In the following class is the code for the thread:
class SomeThread(QtCore.QThread):
finished = QtCore.pyqtSignal(bool)
def __init__(self):
QtCore.QThread.__init__(self)
def __del__(self):
print("del")
self.wait()
def run(self):
self.prog = long_running_prog(self.emit) # Sending from the prog signals
self.prog.run()
self.prog.closeSystem() # Leaving the programm in a safe way.
So if one presses the stop button, the programm should instantly shut down in a save way. Is there a way to abort the class in a save way? For example can I pass a variable to the long_running_prog class which turns True, when one presses the stop button? If somethin like this is possible, could one tell me how?
Thanks for your help in advance
I hope you understand my problem.
Greetings
Hizzy

This is impossible to do unless prog.run(self) would periodically inspect a value of a flag to break out of its loop. Once you implement it, __del__(self) on the thread should set the flag and only then wait.

Related

Python - TypeError: Cannot create graphics instruction outside the main Kivy thread

I'm trying to change the screen after running this function which is a thread. but it gives me the error
TypeError: Cannot create graphics instruction outside the main Kivy thread
I researched online regarding this and they told me to use clock property but I don't know how I can use it here. I just want to be able to change graphic instructions from this command. but if I don't thread it the app goes into not responding.
This is my code:
class Notice(Screen):
def thread_notice(self):
t1 = Thread(target= Notice.sendNotice, args =(self,))
t1.daemon = True
t1.start()
def sendNotice(self):
my_big_chunk_of_code()
kv.current = 'menu'
You cannot do any kivy instructions outside of the main thread, which means you cant run sendNotice on a diffrent thread becuase it has kivy code in it... if my_big_chunk_of_code() takes time then remove the kv.current = 'menu' line and move it to, for example the thread_notice function.
By the way in the thread I suggest making the target: self.sendNotice instead of Notice.sendNotice which i think creates another instance of that class (not sure tho), it should work the same
I am late but well I am posting this answer so that It will help other in future , So Because you can not create graphics instruction outside the main Kivy thread, so you have to be bit tricky about it, I was facing the same problem while ago, So here is how I solve it, So you should first create a popup before starting a thread and than start your thread and if you want to pass argument in your function inside thread, you should use a lambda function like this.
self.showLoadingPopUp() # showing pop up before starting thread
try:
# Using lambda function for passing arguments in function inside thread
threading.Thread(target=lambda : self.yourFunction(argument)).start()
except:
print("Error: unable to start thread")
This will not block your UI and when you create popup function make sure to use a class variable for that, So you can able to close that popup from your thread function, here is the exaple -
class MyApp(App):
def __init__(self):
super().__init__()
self.window = None
self.loading = None
# Main Build function
def build(self):
self.window = GridLayout()
self.window.cols = 2
return self.window
def showLoadingPopUp(self):
content = BoxLayout(orientation='vertical')
self.loading = Popup(title="Loading", title_color='#FFFFFF', content=content, size=('700dp', '200dp'),
size_hint=(None, None),
auto_dismiss=False,
background='#FFFFFF')
content.add_widget(Label(text="Loading...", color="#FFFFFF"))
def _on_d(*args):
self.loading.is_visable = False
self.loading.bind(on_dismiss=_on_d)
self.loading.is_visable = True
self.loading.open()
while not self.loading.is_visable:
EventLoop.idle()
def yourFunction(self, argument):
for i in list:
# code
self.loading.dismiss() # dismissing old popup
So In short this function will first create a popup and than it will start a thread and when our work is done the thread will just close the popup with class variable in the end. Hope it's help.

Run a while loop in pyQt5 using QThread, assigning true and false on stop buttons

I got a project that listens to the microphone if I ever press start. I am so new to pyqt5 so I don't really know Qthreading that much, plus most of the examples are progress bars and for loops. I want that when it presses start it runs the while loop of recording audio for 10 seconds, identifying it, and doing again. And that it only stops when I press stop, a variable is now False. Now this is what I came up with. I am sorry if it looks dumb because I don't really know that much from Qthread. I just want to be able to run this one in particular.
class AvisoWindow(QtWidgets.QMainWindow, Aviso_Main.Ui_MainWindow):
def __init__(self, parent=None):
super(AvisoWindow, self).__init__(parent)
self.setupUi(self)
self.is_Played = True
self.start_btn.clicked.connect(self.startClicked)
self.stop_btn.clicked.connect(self.stopClicked)
def startClicked(self):
while self.is_Played == True:
listen()
time.sleep(0.01)
def stopClicked(self):
self.is_Played = False
self.finished.emit()
if __name__ == "__main__":
app = QApplication(sys.argv)
form = AvisoWindow()
threading.Thread(target=form.__init__(), daemon=True).start()
form.show()
app.exec_()
First of all, nothing related to the UI shall ever be run or accessed in an external thread. Blocking functions and while loops (unless it's guaranteed that they exit almost instantly) should also be avoided, as they block the event loop preventing proper redrawing of the UI and user interaction.
Even assuming that directly using threads with UI was possible, your attempt has two important problems:
__init__() should never be called externally (and, even in that case, it expects at least the instance as argument);
the target argument of Thread expects a callable (a reference to a function), but you're already calling the method (using the parentheses), so it would use the returned value of that called function (which is usually None for an init);
In order to use threads that have to react to an UI environment, in Qt you should create a subclass of QThread that acts as your "worker".
class Worker(QThread):
result = pyqtSignal(str)
def run(self):
self.keepRunning = True
while self.keepRunning:
res = someFunctionThatReturnsAString()
self.result.emit(res)
time.sleep(0.01)
def stop(self):
self.keepRunning = False
class AvisoWindow(QtWidgets.QMainWindow, Aviso_Main.Ui_MainWindow):
def __init__(self, parent=None):
super(AvisoWindow, self).__init__(parent)
self.setupUi(self)
self.worker = Worker()
self.worker.result.connect(self.resultReceived)
self.start_btn.clicked.connect(self.startClicked)
self.stop_btn.clicked.connect(self.stopClicked)
def startClicked(self):
if not self.worker.isRunning():
self.worker.start()
def stopClicked(self):
self.worker.stop()
def resultReceived(self, result):
print(result)

Using QThread to display QProgressDialog in PyQt5 [duplicate]

This question already has an answer here:
Progress Bar Does not Render Until Job is Complete
(1 answer)
Closed 2 years ago.
I am using PyQt5 to write an app that manages Sales Orders. When creating an Order or deleting itI want to display a marqee style progress dialog to indicate that the app is working. I have visited a lot of posts where the answer involved using QThread.I have tried to implement it but it seems I am missing something. This is my threading class.
class Worker(QThread):
finished = Signal()
def run(self):
self.x = QProgressDialog("Please wait..",None,0,0)
self.x.show()
def stop(self):
self.x.close()
In the Main window's init I create self.worker=Worker()
Now the code for deleting an entry is for example:
msg = MsgBox("yn", "Delete Order", "Are you sure you want to delete this order?") # Wrapper for the QMessageBox
if msg == 16384:
self.worker.start() ## start the worker thread, hoping to start the progress dialog
session.delete(order) ##delete order from db
session.commit() ##commit to db
self.change_view("Active", 8) ##func. clean up the table.
self.worker.finished.emit() ##emit the finished signal to close the progress dialog
The result is no progress dialog being displayed. The gui just freezes for a second or two and then the entry deletes without any progress dialog being displayed.
Sorry my code is quite long so I couldn't include it all here, I just wanted to see if I got something terribly wrong.
There are two main problems with your code:
GUI elements (everything inherited or related to a QWidget subclass) must be created and accessed only from the main Qt thread.
assuming that what takes some amount of time is the delete/commit operations, it's those operation that must go in the thread while showing the progress dialog from the main thread, not the other way around.
Also, consider that QThread already has a finished() signal, and you should not overwrite it.
This is an example based on your code:
class Worker(QThread):
def __init__(self, session, order):
super.__init__()
self.session = session
self.order = order
def run(self):
self.session.delete(self.order)
self.session.commit()
class Whatever(QMainWindow):
def __init__(self):
super().__init__()
# ...
self.progressDialog = QProgressDialog("Please wait..", None, 0, 0, self)
def deleteOrder(self, session, order):
msg = MsgBox("yn", "Delete Order",
"Are you sure you want to delete this order?")
if msg == MsgBox.Yes: # you should prefer QMessageBox flags
self.worker = Worker(session, order)
self.worker.started(self.progressDialog.show())
self.worker.finished(self.deleteCompleted)
self.worker.start()
def deleteCompleted(self):
self.progressDialog.hide()
self.change_view("Active", 8)
Since the progress dialog should stay open while processing, you should also prevent the user to be able to close it. To do that you can install an event filter on it and ensure that any close event gets accepted; also, since QProgressDialog inherits from QDialog, the Esc key should be filtered out, otherwise it will not close the dialog, but would reject and hide it.
class Whatever(QMainWindow):
def __init__(self):
super().__init__()
# ...
self.progressDialog = QProgressDialog("Please wait..", None, 0, 0, self)
self.progressDialog.installEventFilter(self)
def eventFilter(self, source, event):
if source == self.progressDialog:
# check for both the CloseEvent *and* the escape key press
if event.type() == QEvent.Close or event == QKeySequence.Cancel:
event.accept()
return True
return super().eventFilter(source, event)

PySide wait for signal from main thread in a worker thread

I decided to add a GUI to one of my scripts. The script is a simple web scraper. I decided to use a worker thread as downloading and parsing the data can take a while. I decided to use PySide, but my knowledge of Qt in general is quite limited.
As the script is supposed to wait for user input upon coming across a captcha I decided it should wait until a QLineEdit fires returnPressed and then send it's content to the worker thread so it can send it for validation. That should be better than busy-waiting for the return key to be pressed.
It seems that waiting for a signal isn't as straight forward as I thought it would be and after searching for a while I came across several solutions similar to this. Signaling across threads and a local event loop in the worker thread make my solution a bit more complicated though.
After tinkering with it for several hours it still won't work.
What is supposed to happen:
Download data until refered to captcha and enter a loop
Download captcha and display it to the user, start QEventLoop by calling self.loop.exec_()
Exit QEventLoop by calling loop.quit() in a worker threads slot which is connected via self.line_edit.returnPressed.connect(self.worker.stop_waiting) in the main_window class
Validate captcha and loop if validation fails, otherwise retry the last url which should be downloadable now, then move on with the next url
What happens:
...see above...
Exiting QEventLoop doesn't work. self.loop.isRunning() returns False after calling its exit(). self.isRunning returns True, as such the thread didn't seem to die under odd circumstances. Still the thread halts at the self.loop.exec_() line. As such the thread is stuck executing the event loop even though the event loop tells me it is not running anymore.
The GUI responds as do the slots of the worker thread class. I can see the text beeing send to the worker thread, the status of the event loop and the thread itself, but nothing after the above mentioned line gets executed.
The code is a bit convoluted, as such I add a bit of pseudo-code-python-mix leaving out the unimportant:
class MainWindow(...):
# couldn't find a way to send the text with the returnPressed signal, so I
# added a helper signal, seems to work though. Doesn't work in the
# constructor, might be a PySide bug?
helper_signal = PySide.QtCore.Signal(str)
def __init__(self):
# ...setup...
self.worker = WorkerThread()
self.line_edit.returnPressed.connect(self.helper_slot)
self.helper_signal.connect(self.worker.stop_waiting)
#PySide.QtCore.Slot()
def helper_slot(self):
self.helper_signal.emit(self.line_edit.text())
class WorkerThread(PySide.QtCore.QThread):
wait_for_input = PySide.QtCore.QEventLoop()
def run(self):
# ...download stuff...
for url in list_of_stuff:
self.results.append(get(url))
#PySide.QtCore.Slot(str)
def stop_waiting(self, text):
self.solution = text
# this definitely gets executed upon pressing return
self.wait_for_input.exit()
# a wrapper for requests.get to handle captcha
def get(self, *args, **kwargs):
result = requests.get(*args, **kwargs)
while result.history: # redirect means captcha
# ...parse and extract captcha...
# ...display captcha to user via not shown signals to main thread...
# wait until stop_waiting stops this event loop and as such the user
# has entered something as a solution
self.wait_for_input.exec_()
# ...this part never get's executed, unless I remove the event
# loop...
post = { # ...whatever data necessary plus solution... }
# send the solution
result = requests.post('http://foo.foo/captcha_url'), data=post)
# no captcha was there, return result
return result
frame = MainWindow()
frame.show()
frame.worker.start()
app.exec_()
What you are describing looks ideal for QWaitCondition.
Simple example:
import sys
from PySide import QtCore, QtGui
waitCondition = QtCore.QWaitCondition()
mutex = QtCore.QMutex()
class Main(QtGui.QMainWindow):
def __init__(self, parent=None):
super(Main, self).__init__()
self.text = QtGui.QLineEdit()
self.text.returnPressed.connect(self.wakeup)
self.worker = Worker(self)
self.worker.start()
self.setCentralWidget(self.text)
def wakeup(self):
waitCondition.wakeAll()
class Worker(QtCore.QThread):
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
def run(self):
print "initial stuff"
mutex.lock()
waitCondition.wait(mutex)
mutex.unlock()
print "after returnPressed"
if __name__=="__main__":
app = QtGui.QApplication(sys.argv)
m = Main()
m.show()
sys.exit(app.exec_())
The slot is executed inside the thread which created the QThread, and not in the thread that the QThread controls.
You need to move a QObject to the thread and connect its slot to the signal, and that slot will be executed inside the thread:
class SignalReceiver(QtCore.QObject):
def __init__(self):
self.eventLoop = QEventLoop(self)
#PySide.QtCore.Slot(str)
def stop_waiting(self, text):
self.text = text
eventLoop.exit()
def wait_for_input(self):
eventLoop.exec()
return self.text
class MainWindow(...):
...
def __init__(self):
...
self.helper_signal.connect(self.worker.signalReceiver.stop_waiting)
class WorkerThread(PySide.QtCore.QThread):
def __init__(self):
self.signalReceiver = SignalReceiver()
# After the following call the slots will be executed in the thread
self.signalReceiver.moveToThread(self)
def get(self, *args, **kwargs):
result = requests.get(*args, **kwargs)
while result.history:
...
self.result = self.signalReceiver.wait_for_input()

How to properly terminate a QThread from a GUI application?

I tried using self.terminate() in the QThread class, and also self.thread.terminate() in the GUI class. I also tried putting self.wait() in both cases. However, there are two scenarios that happen:
1) The thread does not terminate at all, and the GUI freezes waiting for the thread to finish. Once the thread finished, the GUI unfreezes and everything is back to normal.
2) The thread indeed does terminate, but at the same time it freezes the entire application.
I also tried using self.thread.exit(). No joy.
To further clarify, I am trying to implement a user-abort button in GUI which would terminate the executing of the thread at any point in time.
Thanks in advance.
EDIT:
Here is the run() method:
def run(self):
if self.create:
print "calling create f"
self.emit(SIGNAL("disableCreate(bool)"))
self.create(self.password, self.email)
self.stop()
self.emit(SIGNAL("finished(bool)"), self.completed)
def stop(self):
#Tried the following, one by one (and all together too, I was desperate):
self.terminate()
self.quit()
self.exit()
self.stopped = True
self.terminated = True
#Neither works
And here is the GUI class' method for aborting the thread:
def on_abort_clicked(self):
self.thread = threadmodule.Thread()
#Tried the following, also one by one and altogether:
self.thread.exit()
self.thread.wait()
self.thread.quit()
self.thread.terminate()
#Again, none work
From the Qt documentation for QThread::terminate:
Warning: This function is dangerous and its use is discouraged. The
thread can be terminated at any point in its code path. Threads can be
terminated while modifying data. There is no chance for the thread to
clean up after itself, unlock any held mutexes, etc. In short, use
this function only if absolutely necessary.
It's probably a much better idea to re-think your threading strategy such that you can e.g. use QThread::quit() to signal the thread to quit cleanly, rather than trying to get the thread to terminate this way. Actually calling thread.exit() from within the thread should do that depending on how you have implemented run(). If you'd like to share the code for your thread run method that might hint as to why it doesn't work.
This is what I did:
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
stop_flag = 1
...
#=========500 ms class ===================
class Timer500msThread(QThread):
signal_500ms = pyqtSignal(str)
....
def timer500msProcess(self):
if MainWindow.stop_flag == 0 :
self.timer_500ms.stop()
#==========
#=========Main Window ===================
app = QtWidgets.QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
MainWindow.stop_flag=0 #this sets the flag to 0 and when the next 500ms triggers the
#the thread ends
print("Program Ending")
I had a similar problem and solved it with use of pyqtSignals and pyqtSlots.
You could create a pyqtSignal in your MainWindow-class and use the aboutToQuit-function to your QApplication instance.
Then you connect this aboutToQuit-function with another which emit a signal to the slot in your separate thread.
Then you can define a stop() function in this Thread which runs if the signal is emitted.
In this case the thread would not terminate during its work.
MainWindow:
class mainSignals(QObject):
isClosed = pyqtSignal()
class mainwindow(QMainWindow, Ui_MainWindow):
def __init__(self):
super(mainwindow, self).__init__()
self.mainSignal = mainSignals()
...
...
if __name__ = "__main__":
# This function runs if the application is closed.
# (app.aboutToQuit.connect(windowClose))
def windowClose():
window.mainSignal.isClosed.emit() # Emit the signal to the slot in (sepThread)
app = QApplication(sys.argv)
app.aboutToQuit.connect(windowClose)
window = mainwindow()
window.show()
sys.exit(app.exec())
sepThread:
class sepThread(QRunnable):
def __init__(self, parent):
super(sepThread,self).__init__()
self._parent = parent
self.mainOpen = True
self._parent.mainSignal.isClosed.connect(self.stopThread)
# If the signal was emitted by the Mainapplication
# the stopThread function runs and set the mainOpen-attribute to False
def stopThread(self):
self.mainOpen = False
def run(self):
while self.mainOpen == True:
# Do something in a loop while mainOpen-attribute is True
...
...

Categories

Resources