Quitting Tkinter app from menu bar - can't kill thread - python

I have a Python application with a Tkinter GUI. In the app, the user invokes a long-running background process (implemented as a Thread from Python's threading module). I'm having trouble killing the background thread if I quit the program before it's complete. My code works if I quit the application by closing the root window via the 'X' at its top corner, but not if I quit from the top-level menu bar (i.e. Python > Quit or Ctrl+Q). Since most applications use the latter, I'd really like to make that work.
Right now, I kill the background thread with code that looks like this:
class BackgroundCallFrame(Frame):
def __init__(self, parent):
Frame.__init__(self, parent)
self.background_call = BackgroundCall()
self.background_call.start()
class BackgroundCall(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self._stop_req = threading.Event()
def run(self):
for i in range(1,100000):
if self._stop_req.is_set():
return
else:
# do stuff
def stop(self):
self._stop_req.set();
def main():
def kill_all_threads():
if child.background_call is not None:
child.background_call.stop()
child.background_call.join()
root.destroy()
root = Tk()
root.wm_protocol ("WM_DELETE_WINDOW", kill_all_threads)
child = BackgroundCallFrame()
child.pack()
root.mainloop()
if __name__ == '__main__':
main()
How can I make this work if I quit the program without explicitly closing the root window first?
I believe my problem is that kill_all_threads() is not called when I invoke Ctrl+Q, because any print statements I add to it never appear in the console.

Usually you will want to kill a thread by setting a variable in that thread to signal it to stop working. In the run method of your thread you will need to periodically check that variable to know when to exit the run method. Here's an example...
import threading
import time
class WorkerThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.do_stop = False
def run(self):
while True:
time.sleep(.5)
print 'I am running'
if self.do_stop:
return # exit loop and function thus exiting thread
t = WorkerThread()
t.start()
time.sleep(4) # sleep while our thread runs and prints to the command line
print 'setting do_stop to True'
t.do_stop = True # signal to thread to stop
t.join() # wait for thread to exit
print 'all done'
...outputs...
I am running
I am running
I am running
I am running
I am running
I am running
I am running
setting do_stop to True
I am running
all done

Related

How to kill threads in Python with CTRL + C [duplicate]

I am testing Python threading with the following script:
import threading
class FirstThread (threading.Thread):
def run (self):
while True:
print 'first'
class SecondThread (threading.Thread):
def run (self):
while True:
print 'second'
FirstThread().start()
SecondThread().start()
This is running in Python 2.7 on Kubuntu 11.10. Ctrl+C will not kill it. I also tried adding a handler for system signals, but that did not help:
import signal
import sys
def signal_handler(signal, frame):
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
To kill the process I am killing it by PID after sending the program to the background with Ctrl+Z, which isn't being ignored. Why is Ctrl+C being ignored so persistently? How can I resolve this?
Ctrl+C terminates the main thread, but because your threads aren't in daemon mode, they keep running, and that keeps the process alive. We can make them daemons:
f = FirstThread()
f.daemon = True
f.start()
s = SecondThread()
s.daemon = True
s.start()
But then there's another problem - once the main thread has started your threads, there's nothing else for it to do. So it exits, and the threads are destroyed instantly. So let's keep the main thread alive:
import time
while True:
time.sleep(1)
Now it will keep print 'first' and 'second' until you hit Ctrl+C.
Edit: as commenters have pointed out, the daemon threads may not get a chance to clean up things like temporary files. If you need that, then catch the KeyboardInterrupt on the main thread and have it co-ordinate cleanup and shutdown. But in many cases, letting daemon threads die suddenly is probably good enough.
KeyboardInterrupt and signals are only seen by the process (ie the main thread)... Have a look at Ctrl-c i.e. KeyboardInterrupt to kill threads in python
I think it's best to call join() on your threads when you expect them to die. I've taken the liberty to make the change your loops to end (you can add whatever cleanup needs are required to there as well). The variable die is checked on each pass and when it's True, the program exits.
import threading
import time
class MyThread (threading.Thread):
die = False
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run (self):
while not self.die:
time.sleep(1)
print (self.name)
def join(self):
self.die = True
super().join()
if __name__ == '__main__':
f = MyThread('first')
f.start()
s = MyThread('second')
s.start()
try:
while True:
time.sleep(2)
except KeyboardInterrupt:
f.join()
s.join()
An improved version of #Thomas K's answer:
Defining an assistant function is_any_thread_alive() according to this gist, which can terminates the main() automatically.
Example codes:
import threading
def job1():
...
def job2():
...
def is_any_thread_alive(threads):
return True in [t.is_alive() for t in threads]
if __name__ == "__main__":
...
t1 = threading.Thread(target=job1,daemon=True)
t2 = threading.Thread(target=job2,daemon=True)
t1.start()
t2.start()
while is_any_thread_alive([t1,t2]):
time.sleep(0)
One simple 'gotcha' to beware of, are you sure CAPS LOCK isn't on?
I was running a Python script in the Thonny IDE on a Pi4. With CAPS LOCK on, Ctrl+Shift+C is passed to the keyboard buffer, not Ctrl+C.

Celery: Stop an infinite loop running as a background thread

I am trying to utilize threading.Thread with Celery to work like a daemon. On a larger scope, the threads will poll hardware sensors as a part of a web UI-powered thermostat, but narrowed-down, this is the bit I'm stuck on:
from celery import Celery
from celery.signals import worker_init
from celery.signals import worker_process_shutdown
from threading import Thread
from threading import Event
from time import sleep
class ThisClass(object):
def __init__(self):
self.shutdown = Event()
self.thread = Thread(target=self.BackgroundMethod)
def Start(self):
self.thread.start()
def Stop(self):
self.shutdown.set()
def BackgroundMethod(self):
while not self.shutdown.is_set():
print("Hello, world!")
sleep(1)
this_class = ThisClass()
celery_app = Celery("tasks", broker="amqp://guest#localhost//")
#worker_init.connect
def WorkerReady(**kwargs):
this_class.Start()
#worker_process_shutdown.connect
def StopPollingSensors(**kwargs):
this_class.Stop()
This Celery script is supposed to create an instance of ThisClass as this_class, and run this_class.Start() when Celery starts. When Celery is shutting down, it is supposed to call this_class.Stop(), which gracefully exits the Thread in ThisClass and Celery cleanly exits.
However, when I hit Ctrl-C in Celery to signal a SIGINT, this_class's thread continues to run and Celery does not exit, even after multiple SIGTERMs are issued. What confuses me is if I slip a print statement in ThisClass.Stop, I see it. Furthermore, if I add sleep(5); this_class.Stop() after this_class.Start(), the thread starts and stops as expected, and Celery will exit normally when issued a SIGINT.
How am I supposed to terminate threading.Thread instances in a Celery-based script?
Consider creating the Event object externally, and passing it to a Thread subclass. The thread loops on the event object. When an external function calls Event.set(), the thread loop exits out of the while loop cleanly.
import sys, time
from threading import Event, Thread, Timer
class ThisClass2(Thread):
def __init__(self, event):
super(ThisClass2,self).__init__()
self.event = event
def run(self):
print 'thread start'
while not self.event.wait(timeout=1.0):
print("thread: Hello, world!")
print 'thread done'
event2 = Event()
x = ThisClass2(event2)
x.start()
print 'okay'
time.sleep(1)
print 'signaling event'
event2.set()
print 'waiting'
x.join()
Example output:
thread start
okay
thread: Hello, world!
signaling event
waiting
thread done

Cannot kill Python script with Ctrl-C

I am testing Python threading with the following script:
import threading
class FirstThread (threading.Thread):
def run (self):
while True:
print 'first'
class SecondThread (threading.Thread):
def run (self):
while True:
print 'second'
FirstThread().start()
SecondThread().start()
This is running in Python 2.7 on Kubuntu 11.10. Ctrl+C will not kill it. I also tried adding a handler for system signals, but that did not help:
import signal
import sys
def signal_handler(signal, frame):
sys.exit(0)
signal.signal(signal.SIGINT, signal_handler)
To kill the process I am killing it by PID after sending the program to the background with Ctrl+Z, which isn't being ignored. Why is Ctrl+C being ignored so persistently? How can I resolve this?
Ctrl+C terminates the main thread, but because your threads aren't in daemon mode, they keep running, and that keeps the process alive. We can make them daemons:
f = FirstThread()
f.daemon = True
f.start()
s = SecondThread()
s.daemon = True
s.start()
But then there's another problem - once the main thread has started your threads, there's nothing else for it to do. So it exits, and the threads are destroyed instantly. So let's keep the main thread alive:
import time
while True:
time.sleep(1)
Now it will keep print 'first' and 'second' until you hit Ctrl+C.
Edit: as commenters have pointed out, the daemon threads may not get a chance to clean up things like temporary files. If you need that, then catch the KeyboardInterrupt on the main thread and have it co-ordinate cleanup and shutdown. But in many cases, letting daemon threads die suddenly is probably good enough.
KeyboardInterrupt and signals are only seen by the process (ie the main thread)... Have a look at Ctrl-c i.e. KeyboardInterrupt to kill threads in python
I think it's best to call join() on your threads when you expect them to die. I've taken the liberty to make the change your loops to end (you can add whatever cleanup needs are required to there as well). The variable die is checked on each pass and when it's True, the program exits.
import threading
import time
class MyThread (threading.Thread):
die = False
def __init__(self, name):
threading.Thread.__init__(self)
self.name = name
def run (self):
while not self.die:
time.sleep(1)
print (self.name)
def join(self):
self.die = True
super().join()
if __name__ == '__main__':
f = MyThread('first')
f.start()
s = MyThread('second')
s.start()
try:
while True:
time.sleep(2)
except KeyboardInterrupt:
f.join()
s.join()
An improved version of #Thomas K's answer:
Defining an assistant function is_any_thread_alive() according to this gist, which can terminates the main() automatically.
Example codes:
import threading
def job1():
...
def job2():
...
def is_any_thread_alive(threads):
return True in [t.is_alive() for t in threads]
if __name__ == "__main__":
...
t1 = threading.Thread(target=job1,daemon=True)
t2 = threading.Thread(target=job2,daemon=True)
t1.start()
t2.start()
while is_any_thread_alive([t1,t2]):
time.sleep(0)
One simple 'gotcha' to beware of, are you sure CAPS LOCK isn't on?
I was running a Python script in the Thonny IDE on a Pi4. With CAPS LOCK on, Ctrl+Shift+C is passed to the keyboard buffer, not Ctrl+C.

Periodic Python Threads with on demand trigger

I have simple PyGTK app. Since I have to run multiple periodic tasks to fetch some data and refresh GUI, I extended Thread like this:
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.setDaemon(True)
self.event = threading.Event()
self.event.set()
def run(self):
while self.event.is_set():
timer = threading.Timer(60, self._run)
timer.start()
timer.join()
def cancel(self):
self.event.clear()
def _run(self):
gtk.threads_enter()
# do what need to be done, fetch data, update GUI
gtk.threads_leave()
I start threads on app bootstrap, save them in some list and cancel them before exit. This works just perfect.
But now I want to add refresh button which will force one of the threads to run immediately and not wait period of time to be run, if not currently running.
I tried to do that by adding bool var to MyThread to indicate whether a thread is running or not (set before _run, reset on complete), and then just call MyThread._run() if not running, but that causes my app to become unresponsive and _run task to never finish execution.
I'm not sure why this happens. What is the best way to solve this problem? It would be also fine if I can make refresh running in background so it does not block GUI.
Maybe to call run and pass in number of seconds to 1 so timer can trigger it sooner?
Instead of using a Timer, use another Event object in combination with a timeout. You can then set that event from within your button callback. The following code illustrates this (I've stripped your cancelling code to keep it short):
import threading
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.sleep_event = threading.Event()
self.damon = True
def run(self):
while True:
self.sleep_event.clear()
self.sleep_event.wait(60)
threading.Thread(target=self._run).start()
def _run(self):
print "run"
my_thread = MyThread()
my_thread.start()
while True:
raw_input("Hit ENTER to force execution\n")
my_thread.sleep_event.set()
By default "run" will be printed every 60 seconds. If you hit ENTER it will be printed immediately, and then again after 60 seconds, etc.

Non-blocking class in python (detached Thread)

I'm trying to create a kind of non-blocking class in python, but I'm not sure how.
I'd like a class to be a thread itself, detached from the main thread so other threads can interact with it.
In a little example:
#!/usr/bin/python2.4
import threading
import time
class Sample(threading.Thread):
def __init__(self):
super(Sample, self).__init__()
self.status = 1
self.stop = False
def run(self):
while not(self.stop):
pass
def getStatus(self):
return self.status
def setStatus(self, status):
self.status = status
def test(self):
while self.status != 0:
time.sleep(2)
#main
sample = Sample()
sample.start()
sample.test()
sample.setStatus(0)
sample.stop()
What I'd like is having the "sample" instance running as a separate thread (detached from the main one) so, in the example, when the main thread reaches sample.test(), sample (and only "sample") would go to sleep for 2 seconds. In the meanwhile, the main thread would continue its execution and set sample's status to 0. When after the 2 seconds "sample" wakes up it would see the status=0 and exit the while loop.
The problem is that if I do this, the line sample.setStatus(0) is never reached (creating an infinite loop). I have named the threads, and it turns out that by doing this, test() is run by the main thread.
I guess I don't get the threading in python that well...
Thank you in advance
The object's run() method is what executes in a separate thread. When you call sample.test(), that executes in the main thread, so you get your infinite loop.
Perhaps something like this?
import threading
import time
class Sample(threading.Thread):
def __init__(self):
super(Sample, self).__init__()
self.stop = False
def run(self):
while not(self.stop):
print('hi')
time.sleep(.1)
def test(self):
print('testing...')
time.sleep(2)
#main
sample = Sample()
sample.start() # Initiates second thread which calls sample.run()
sample.test() # Main thread calls sample.test
sample.stop=True # Main thread sets sample.stop
sample.join() # Main thread waits for second thread to finish

Categories