Starting a new thread in Gui application python - python-3.x

I've got a python Gui application which has a thread that does some updating.
This is how its implemented.
GObject.threads_init()
Class main:
#Extra stuff here
update_thread = Thread(target= update_func, args=(Blah blah,))
update_thread.setDaemon(True)
update_thread.start()
Gtk.main()
This is how the update_func looke like
def update_func():
try:
#do updating
time.sleep(#6hrs)
except:
#catch error
time.sleep(#5 min)
finally:
update_func()
The thread runs as long as the program is running and the program I have runs for days
The problem is that sometimes the thread dies and updates do not occur and I have to restart the application.
Is there a way to start a new thread if the current one dies, especially in a Gui application?

Below is a thread example snipped from one of my gtk apps. It might be helpful.
#!/usr/bin/env python3
# Copyright (C) 2013 LiuLang <gsushzhsosgsu#gmail.com>
# Use of this source code is governed by GPLv3 license that can be found
# in http://www.gnu.org/licenses/gpl-3.0.html
from gi.repository import GObject
from gi.repository import Gtk
import threading
#UPDATE_INTERVAL = 6 * 60 * 60 * 1000 # 6 hours
UPDATE_INTERVAL = 2 * 1000 # 2 secs, for test only
def async_call(func, func_done, *args):
'''
Call func in another thread, without blocking gtk main loop.
`func` does time-consuming job in background, like access website.
If `func_done` is not None, it will be called after func() ends.
func_done is called in gtk main thread, and this function is often used
to update GUI widgets in app.
`args` are parameters for func()
'''
def do_call(*args):
result = None
error = None
try:
result = func(*args)
except Exception as e:
error = e
if func_done is not None:
GObject.idle_add(lambda: func_done(result, error))
thread = threading.Thread(target=do_call, args=args)
thread.start()
class App(Gtk.Window):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.set_default_size(480, 320)
self.set_border_width(5)
self.set_title('Gtk Threads')
self.connect('delete-event', self.on_app_exit)
self.update_timer = GObject.timeout_add(UPDATE_INTERVAL,
self.check_update)
def run(self):
self.show_all()
Gtk.main()
def on_app_exit(self, *args):
Gtk.main_quit()
def check_update(self):
def _do_check_update():
print('do check update: will check for updating info')
print(threading.current_thread(), '\n')
print('check update')
print(threading.current_thread())
async_call(_do_check_update, None)
return True
if __name__ == '__main__':
app = App()
app.run()

Related

Ctrl+c not stopping a Thread in Windows + python3.7

I'm trying this simple thread with a while loop inside. When I'm inside the while loop, Ctrl+C has no effect in stopping my program. Once I go do something else after the while loop, the script stops as intended. What can I do so my script can be gracefully killed both while being in the while loop and after? (Edit: This seems to be a problem exclusive to Windows, iOS and Ubuntu seem to do what I want)
import time, threading
class MainClass(threading.Thread):
def __init__(self):
super().__init__()
def run(self):
while True:
time.sleep(1)
print("Looping")
# Script entry point
if __name__ == '__main__':
a = MainClass()
a.daemon = True
a.start()
a.join()
This is a known issue, explained in Issue 35935.
A way to solve it is to revert to the default kernel behaviour of SIGINT using signal.signal(signal.SIGINT, signal.SIG_DFL), (solution pointed out by the issue OP). As to why this has to be the case is beyond the scope of my knowledge.
This works on Windows using Python 3.8+.
Implementation:
import time, threading
import signal
class MainClass(threading.Thread):
def __init__(self):
super().__init__()
def run(self):
while True:
time.sleep(1)
print("Looping")
# Script entry point
if __name__ == '__main__':
a = MainClass()
a.daemon=True
signal.signal(signal.SIGINT, signal.SIG_DFL)
a.start()
a.join()

Python PyQt5: based on condition, run a CPU intensive QThread [duplicate]

I am trying to figure out why this code crashes if I try to run the threads for a second time once they are completed.
The first time I click "Start 5 Threads" It runs just fine and finishes. But if I click it again. The entire program crashes and I get the QThread: Destroyed while thread is still running Error
This code was found on the web. I am trying to learn from it.
import time
import sys
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidget
def trap_exc_during_debug(*args):
# when app raises uncaught exception, print info
print(args)
# install exception hook: without this, uncaught exception would cause application to exit
sys.excepthook = trap_exc_during_debug
class Worker(QObject):
"""
Must derive from QObject in order to emit signals, connect slots to other signals, and operate in a QThread.
"""
sig_step = pyqtSignal(int, str) # worker id, step description: emitted every step through work() loop
sig_done = pyqtSignal(int) # worker id: emitted at end of work()
sig_msg = pyqtSignal(str) # message to be shown to user
def __init__(self, id: int):
super().__init__()
self.__id = id
self.__abort = False
#pyqtSlot()
def work(self):
"""
Pretend this worker method does work that takes a long time. During this time, the thread's
event loop is blocked, except if the application's processEvents() is called: this gives every
thread (incl. main) a chance to process events, which in this sample means processing signals
received from GUI (such as abort).
"""
thread_name = QThread.currentThread().objectName()
thread_id = int(QThread.currentThreadId()) # cast to int() is necessary
self.sig_msg.emit('Running worker #{} from thread "{}" (#{})'.format(self.__id, thread_name, thread_id))
for step in range(100):
time.sleep(0.1)
self.sig_step.emit(self.__id, 'step ' + str(step))
# check if we need to abort the loop; need to process events to receive signals;
app.processEvents() # this could cause change to self.__abort
if self.__abort:
# note that "step" value will not necessarily be same for every thread
self.sig_msg.emit('Worker #{} aborting work at step {}'.format(self.__id, step))
break
self.sig_done.emit(self.__id)
def abort(self):
self.sig_msg.emit('Worker #{} notified to abort'.format(self.__id))
self.__abort = True
class MyWidget(QWidget):
NUM_THREADS = 5
# sig_start = pyqtSignal() # needed only due to PyCharm debugger bug (!)
sig_abort_workers = pyqtSignal()
def __init__(self):
super().__init__()
self.setWindowTitle("Thread Example")
form_layout = QVBoxLayout()
self.setLayout(form_layout)
self.resize(400, 800)
self.button_start_threads = QPushButton()
self.button_start_threads.clicked.connect(self.start_threads)
self.button_start_threads.setText("Start {} threads".format(self.NUM_THREADS))
form_layout.addWidget(self.button_start_threads)
self.button_stop_threads = QPushButton()
self.button_stop_threads.clicked.connect(self.abort_workers)
self.button_stop_threads.setText("Stop threads")
self.button_stop_threads.setDisabled(True)
form_layout.addWidget(self.button_stop_threads)
self.log = QTextEdit()
form_layout.addWidget(self.log)
self.progress = QTextEdit()
form_layout.addWidget(self.progress)
QThread.currentThread().setObjectName('main') # threads can be named, useful for log output
self.__workers_done = None
self.__threads = None
def start_threads(self):
self.log.append('starting {} threads'.format(self.NUM_THREADS))
self.button_start_threads.setDisabled(True)
self.button_stop_threads.setEnabled(True)
self.__workers_done = 0
self.__threads = []
for idx in range(self.NUM_THREADS):
worker = Worker(idx)
thread = QThread()
thread.setObjectName('thread_' + str(idx))
self.__threads.append((thread, worker)) # need to store worker too otherwise will be gc'd
worker.moveToThread(thread)
# get progress messages from worker:
worker.sig_step.connect(self.on_worker_step)
worker.sig_done.connect(self.on_worker_done)
worker.sig_msg.connect(self.log.append)
# control worker:
self.sig_abort_workers.connect(worker.abort)
# get read to start worker:
# self.sig_start.connect(worker.work) # needed due to PyCharm debugger bug (!); comment out next line
thread.started.connect(worker.work)
thread.start() # this will emit 'started' and start thread's event loop
# self.sig_start.emit() # needed due to PyCharm debugger bug (!)
#pyqtSlot(int, str)
def on_worker_step(self, worker_id: int, data: str):
self.log.append('Worker #{}: {}'.format(worker_id, data))
self.progress.append('{}: {}'.format(worker_id, data))
#pyqtSlot(int)
def on_worker_done(self, worker_id):
self.log.append('worker #{} done'.format(worker_id))
self.progress.append('-- Worker {} DONE'.format(worker_id))
self.__workers_done += 1
if self.__workers_done == self.NUM_THREADS:
self.log.append('No more workers active')
self.button_start_threads.setEnabled(True)
self.button_stop_threads.setDisabled(True)
# self.__threads = None
#pyqtSlot()
def abort_workers(self):
self.sig_abort_workers.emit()
self.log.append('Asking each worker to abort')
for thread, worker in self.__threads: # note nice unpacking by Python, avoids indexing
thread.quit() # this will quit **as soon as thread event loop unblocks**
thread.wait() # <- so you need to wait for it to *actually* quit
# even though threads have exited, there may still be messages on the main thread's
# queue (messages that threads emitted before the abort):
self.log.append('All threads exited')
if __name__ == "__main__":
app = QApplication([])
form = MyWidget()
form.show()
sys.exit(app.exec_())
The problem is solved by passing him as a parent to self. You must change:
thread = QThread()
to:
thread = QThread(parent=self)

PyQt - Signaling worker in QThread [duplicate]

I am trying to figure out why this code crashes if I try to run the threads for a second time once they are completed.
The first time I click "Start 5 Threads" It runs just fine and finishes. But if I click it again. The entire program crashes and I get the QThread: Destroyed while thread is still running Error
This code was found on the web. I am trying to learn from it.
import time
import sys
from PyQt5.QtCore import QObject, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QApplication, QPushButton, QTextEdit, QVBoxLayout, QWidget
def trap_exc_during_debug(*args):
# when app raises uncaught exception, print info
print(args)
# install exception hook: without this, uncaught exception would cause application to exit
sys.excepthook = trap_exc_during_debug
class Worker(QObject):
"""
Must derive from QObject in order to emit signals, connect slots to other signals, and operate in a QThread.
"""
sig_step = pyqtSignal(int, str) # worker id, step description: emitted every step through work() loop
sig_done = pyqtSignal(int) # worker id: emitted at end of work()
sig_msg = pyqtSignal(str) # message to be shown to user
def __init__(self, id: int):
super().__init__()
self.__id = id
self.__abort = False
#pyqtSlot()
def work(self):
"""
Pretend this worker method does work that takes a long time. During this time, the thread's
event loop is blocked, except if the application's processEvents() is called: this gives every
thread (incl. main) a chance to process events, which in this sample means processing signals
received from GUI (such as abort).
"""
thread_name = QThread.currentThread().objectName()
thread_id = int(QThread.currentThreadId()) # cast to int() is necessary
self.sig_msg.emit('Running worker #{} from thread "{}" (#{})'.format(self.__id, thread_name, thread_id))
for step in range(100):
time.sleep(0.1)
self.sig_step.emit(self.__id, 'step ' + str(step))
# check if we need to abort the loop; need to process events to receive signals;
app.processEvents() # this could cause change to self.__abort
if self.__abort:
# note that "step" value will not necessarily be same for every thread
self.sig_msg.emit('Worker #{} aborting work at step {}'.format(self.__id, step))
break
self.sig_done.emit(self.__id)
def abort(self):
self.sig_msg.emit('Worker #{} notified to abort'.format(self.__id))
self.__abort = True
class MyWidget(QWidget):
NUM_THREADS = 5
# sig_start = pyqtSignal() # needed only due to PyCharm debugger bug (!)
sig_abort_workers = pyqtSignal()
def __init__(self):
super().__init__()
self.setWindowTitle("Thread Example")
form_layout = QVBoxLayout()
self.setLayout(form_layout)
self.resize(400, 800)
self.button_start_threads = QPushButton()
self.button_start_threads.clicked.connect(self.start_threads)
self.button_start_threads.setText("Start {} threads".format(self.NUM_THREADS))
form_layout.addWidget(self.button_start_threads)
self.button_stop_threads = QPushButton()
self.button_stop_threads.clicked.connect(self.abort_workers)
self.button_stop_threads.setText("Stop threads")
self.button_stop_threads.setDisabled(True)
form_layout.addWidget(self.button_stop_threads)
self.log = QTextEdit()
form_layout.addWidget(self.log)
self.progress = QTextEdit()
form_layout.addWidget(self.progress)
QThread.currentThread().setObjectName('main') # threads can be named, useful for log output
self.__workers_done = None
self.__threads = None
def start_threads(self):
self.log.append('starting {} threads'.format(self.NUM_THREADS))
self.button_start_threads.setDisabled(True)
self.button_stop_threads.setEnabled(True)
self.__workers_done = 0
self.__threads = []
for idx in range(self.NUM_THREADS):
worker = Worker(idx)
thread = QThread()
thread.setObjectName('thread_' + str(idx))
self.__threads.append((thread, worker)) # need to store worker too otherwise will be gc'd
worker.moveToThread(thread)
# get progress messages from worker:
worker.sig_step.connect(self.on_worker_step)
worker.sig_done.connect(self.on_worker_done)
worker.sig_msg.connect(self.log.append)
# control worker:
self.sig_abort_workers.connect(worker.abort)
# get read to start worker:
# self.sig_start.connect(worker.work) # needed due to PyCharm debugger bug (!); comment out next line
thread.started.connect(worker.work)
thread.start() # this will emit 'started' and start thread's event loop
# self.sig_start.emit() # needed due to PyCharm debugger bug (!)
#pyqtSlot(int, str)
def on_worker_step(self, worker_id: int, data: str):
self.log.append('Worker #{}: {}'.format(worker_id, data))
self.progress.append('{}: {}'.format(worker_id, data))
#pyqtSlot(int)
def on_worker_done(self, worker_id):
self.log.append('worker #{} done'.format(worker_id))
self.progress.append('-- Worker {} DONE'.format(worker_id))
self.__workers_done += 1
if self.__workers_done == self.NUM_THREADS:
self.log.append('No more workers active')
self.button_start_threads.setEnabled(True)
self.button_stop_threads.setDisabled(True)
# self.__threads = None
#pyqtSlot()
def abort_workers(self):
self.sig_abort_workers.emit()
self.log.append('Asking each worker to abort')
for thread, worker in self.__threads: # note nice unpacking by Python, avoids indexing
thread.quit() # this will quit **as soon as thread event loop unblocks**
thread.wait() # <- so you need to wait for it to *actually* quit
# even though threads have exited, there may still be messages on the main thread's
# queue (messages that threads emitted before the abort):
self.log.append('All threads exited')
if __name__ == "__main__":
app = QApplication([])
form = MyWidget()
form.show()
sys.exit(app.exec_())
The problem is solved by passing him as a parent to self. You must change:
thread = QThread()
to:
thread = QThread(parent=self)

tkinter window not closing properly

My tkinter app windows are not closing properly. I am using python 3.6.6 with tkinter 8.6
My code does basically this:
Open a process, where:
A test function is called via a thread that closes Gui window after 3s
Gui window is created
Wait for thread to complete (join) and guess window was closed
I tried to use:
quit -> window only closes when i hover my mouse over it
destroy -> destroy does not return
I stripped it down to the following code, please copy & execute and/or tell me whats wrong...
from time import sleep, time
import threading
from multiprocessing import Process, set_start_method
from tkinter import *
CtrlApplObj = None
def Start():
global CtrlApplObj
CtrlApplObj = None
CtrlApplObj = ControlApplication()
CtrlApplObj.run()
def End():
print("Quit now...")
#CtrlApplObj.root.destroy()
CtrlApplObj.root.quit()
class ControlApplication():
def __init__(self):
pass
def run(self):
self.root=Tk()
print("Mainloop...")
self.root.mainloop()
def test():
sleep(3)
End()
def execute():
T1 = threading.Thread(target=test)
T1.start()
Start()
T1.join()
if __name__ == "__main__":
set_start_method("spawn")
for i in range(2):
TestProcess = Process(target=execute)
TestProcess.start()
TestProcess.join()
My final solution was not using any tkinter operations in test thread. Then destroy worked.
I had another problem with my test process not closing. This was because a queue was not empty. That porevented process to close.

PyQt MainWindow using multiprocessing on Windows

I try to create a PyQt application. In order to run process in background and keep the PyQt5 application available for new instruction, I want to use multiprocessing.
On the Windows OS, when I call a function from the Qt MainWindow class with multiprocessing.process, I have an error about pickling this class. But it is running find on Linux.
Here is an example:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import multiprocessing
#
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5.QtCore import *
class Frame_main(QMainWindow):
def __init__(self, parent = None):
super(Frame_main, self).__init__(parent)
self.thrd_list=[]
self.initUI()
def initUI(self):
# Button
btn_run = QPushButton('Run', self)
btn_run.clicked.connect(lambda: self.ThdSomething(self.DoRun) ) #
btn_stop = QPushButton('Stop', self)
btn_stop.clicked.connect(self.DoStop)
### TEXT Edit
self.textEdit_term = QTextEdit("terminal: \n ")
self.textEdit_term.append("")
self.textEdit_term.setStyleSheet("color: rgb(255, 255, 255); background-color: black;")
self.textEdit_term.setLineWrapMode(QTextEdit.NoWrap)
self.textEdit_term.setToolTip(' terminal message ')
self.textEdit_term.setStatusTip('textEdit1')
### LAYOUT
Wid_main = QWidget() #
grid_major = QGridLayout() #
grid_major.addWidget( btn_run, 1, 5)
grid_major.addWidget( btn_stop, 2, 5)
Wid_main.setLayout(grid_major)
self.setCentralWidget(Wid_main)
### Affichage
self.show() #
#
def DoRun(self):
print('g starts')
time_start=time.time()
time_stop=time.time()
name='bob'
n=0
while time_stop-time_start <2 :
n+=1
time_stop=time.time()
time.sleep(0.8)
print ('hola', name,n, flush=True)
print('g stops')
def DoStop(self):
''' subourtine kill all the thread '''
print('stop action detected')
while len(self.thrd_list) > 0 :
print("Terminating the job: {}".format(self.thrd[-1].pid) )
os.kill(self.thrd[-1].pid, signal.SIGTERM)
self.thrd_list[-1].terminate()
self.thrd_list.pop()
def ThdSomething(self, job):
''' subourtine to create a new thread to do the job subroutine '''
arg=''
p=multiprocessing.Process(target=job, args=(arg))
self.thrd_list.append( p )
p.start()
print("Start the job GUI: {} with PID: {}".format(str(job) ,self.thrd[-1].pid), file=sys.stdout )
def closeEvent(self, event):
''' subroutine to define what happen when closing the main frame'''
self.statusBar().showMessage('waiting for a respond')
reply = QMessageBox.question(self, 'Message',
"Are you sure to quit?", QMessageBox.Yes |
QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
event.accept()
else:
event.ignore()
# Main
if __name__ == '__main__':
# Create QApplication and Main Frame
Qapp = QApplication(sys.argv) # creation de lappli Qt
Qapp.setStyle("fusion") #
frame1 = Frame_main() #
sys.exit(Qapp.exec_()) #
EDIT:
I found several similar case, like :
Python: multiprocessing in pyqt application
but none of them helped. I think this might be linked to fact that my case is using function and attributes of the MainWindow class.
You are correct that this is due to the attempt to fork on a method within the GUI class. Unfortunately windows doesn't really have the ability to fork processes the way linux does, which is why your code works on linux and not on windows. The Python 3 documentation for the multiprocessing library has some useful information on the differences and what the default method for starting a new process is under different platforms.
To answer your question: because your target is a method associated with a GUI object, it has to send the object state to the new process but fails to pickle it because a PyQt GUI object is far too complex to pickle.
If you rewrite your code so that the target function (and args specified) are picklable, your code will work.

Resources