Trigger an event when a thread is finished
Question:
Hi Python and Tkinter Gurus,
I am trying to build a simple GUI which has two buttons. When the Button is clicked, a thread is started to do some work. This work normally takes 10s/15s. And the GUI also works fine.
But I want to implement a pop-up to notify which thread has been completed. I have checked t.isAlive()
function and could not implement it because I don’t know how to trigger an event based on isAlive in the main loop.
Here is my sample code
from threading import Thread
from time import sleep
import Tkinter
import ttk
class SmallGui:
def __init__(self, master):
self.master = master
self.master.title('test gui')
self.button_1 = ttk.Button(self.master,
text='Start 1',
command=lambda: self.init_thread(1))
self.button_2 = ttk.Button(self.master,
text='Start 2',
command=lambda: self.init_thread(2))
self.button_1.pack()
self.button_2.pack()
def init_thread(self, work):
if work == 1:
t = Thread(target=self.work_1)
t.start()
else:
t = Thread(target=self.work_2)
t.start()
@staticmethod
def work_1():
print 'Work 1 started'
# Do some Task and return a list
sleep(10)
@staticmethod
def work_2():
print 'Work 2 Started'
# Do some Task and return a list
sleep(15)
if __name__ == '__main__':
root = Tkinter.Tk()
run_gui = SmallGui(root)
root.mainloop()
Answers:
You can use tkinter’s messagebox. Tkinter has a built in method that can be used for all kinds of pop-up messages or questions. Here we will use messagebox.showinfo
.
I am working on Python 3.X so I added a import method that will work for both 3.X and 2.X versions of python.
from threading import Thread
from time import sleep
try:
import Tkinter as tk
import tkMessageBox as mb
import ttk
except ImportError:
import tkinter as tk
from tkinter import messagebox as mb
import tkinter.ttk as ttk
class SmallGui:
def __init__(self, master):
self.master = master
self.master.title('test gui')
self.button_1 = ttk.Button(self.master,
text='Start 1',
command=lambda: self.init_thread(1))
self.button_2 = ttk.Button(self.master,
text='Start 2',
command=lambda: self.init_thread(2))
self.button_1.pack()
self.button_2.pack()
def init_thread(self, work):
if work == 1:
t = Thread(target=self.work_1)
t.start()
else:
t = Thread(target=self.work_2)
t.start()
@staticmethod
def work_1():
print ('Work 1 started')
# Do some Task and return a list
sleep(1)
mb.showinfo("test", "Work 1 complete")
@staticmethod
def work_2():
print ('Work 2 Started')
# Do some Task and return a list
sleep(1)
mb.showinfo("test", "Work 2 complete")
if __name__ == '__main__':
root = tk.Tk()
run_gui = SmallGui(root)
root.mainloop()
UPDATE:
For whatever reason my above solution works in python 3 but no in 2.7.14.
The below example however does work in 2.7.14 and should work for you.
What I have done here is create 2 class attributes to monitor each thread.
I have created a method that will check ever 1 second if a thread is active and if the thread becomes inactive a messagebox will popup.
from threading import Thread
from time import sleep
import Tkinter as tk
import tkMessageBox as mb
import ttk
class SmallGui(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.master.title('test gui')
self.button_1 = ttk.Button(self.master,
text='Start 1',
command=lambda: self.init_thread(1))
self.button_2 = ttk.Button(self.master,
text='Start 2',
command=lambda: self.init_thread(2))
self.button_1.pack()
self.button_2.pack()
self.work1_status = None
self.work2_status = None
def init_thread(self, work):
if work == 1:
self.work1_status = Thread(target=self.work_1)
self.work1_status.start()
self.check_thread(self.work1_status, work)
else:
self.work2_status = Thread(target=self.work_2)
self.work2_status.start()
self.check_thread(self.work2_status, work)
def check_thread(self, pass_thread, thread_name):
if pass_thread.isAlive() == False:
pass_thread = None
mb.showinfo("test", "Work {} complete".format(thread_name))
else:
self.after(1000, lambda: self.check_thread(pass_thread, thread_name))
@staticmethod
def work_1():
print ('Work 1 started')
# Do some Task and return a list
sleep(5)
@staticmethod
def work_2():
print ('Work 2 Started')
# Do some Task and return a list
sleep(5)
if __name__ == '__main__':
root = tk.Tk()
run_gui = SmallGui(root)
root.mainloop()
A easy solution to check thread finished or not. It is thread safe
Install pyrvsignal
pip install pyrvsignal
Example:
import time
from threading import Thread
from pyrvsignal import Signal
class MyThread(Thread):
started = Signal()
finished = Signal()
def __init__(self, target, args):
self.target = target
self.args = args
Thread.__init__(self)
def run(self) -> None:
self.started.emit()
self.target(*self.args)
self.finished.emit()
def do_my_work(details):
print(f"Doing work: {details}")
time.sleep(10)
def started_work():
print("Started work")
def finished_work():
print("Work finished")
thread = MyThread(target=do_my_work, args=("testing",))
thread.started.connect(started_work)
thread.finished.connect(finished_work)
thread.start()
Hi Python and Tkinter Gurus,
I am trying to build a simple GUI which has two buttons. When the Button is clicked, a thread is started to do some work. This work normally takes 10s/15s. And the GUI also works fine.
But I want to implement a pop-up to notify which thread has been completed. I have checked t.isAlive()
function and could not implement it because I don’t know how to trigger an event based on isAlive in the main loop.
Here is my sample code
from threading import Thread
from time import sleep
import Tkinter
import ttk
class SmallGui:
def __init__(self, master):
self.master = master
self.master.title('test gui')
self.button_1 = ttk.Button(self.master,
text='Start 1',
command=lambda: self.init_thread(1))
self.button_2 = ttk.Button(self.master,
text='Start 2',
command=lambda: self.init_thread(2))
self.button_1.pack()
self.button_2.pack()
def init_thread(self, work):
if work == 1:
t = Thread(target=self.work_1)
t.start()
else:
t = Thread(target=self.work_2)
t.start()
@staticmethod
def work_1():
print 'Work 1 started'
# Do some Task and return a list
sleep(10)
@staticmethod
def work_2():
print 'Work 2 Started'
# Do some Task and return a list
sleep(15)
if __name__ == '__main__':
root = Tkinter.Tk()
run_gui = SmallGui(root)
root.mainloop()
You can use tkinter’s messagebox. Tkinter has a built in method that can be used for all kinds of pop-up messages or questions. Here we will use messagebox.showinfo
.
I am working on Python 3.X so I added a import method that will work for both 3.X and 2.X versions of python.
from threading import Thread
from time import sleep
try:
import Tkinter as tk
import tkMessageBox as mb
import ttk
except ImportError:
import tkinter as tk
from tkinter import messagebox as mb
import tkinter.ttk as ttk
class SmallGui:
def __init__(self, master):
self.master = master
self.master.title('test gui')
self.button_1 = ttk.Button(self.master,
text='Start 1',
command=lambda: self.init_thread(1))
self.button_2 = ttk.Button(self.master,
text='Start 2',
command=lambda: self.init_thread(2))
self.button_1.pack()
self.button_2.pack()
def init_thread(self, work):
if work == 1:
t = Thread(target=self.work_1)
t.start()
else:
t = Thread(target=self.work_2)
t.start()
@staticmethod
def work_1():
print ('Work 1 started')
# Do some Task and return a list
sleep(1)
mb.showinfo("test", "Work 1 complete")
@staticmethod
def work_2():
print ('Work 2 Started')
# Do some Task and return a list
sleep(1)
mb.showinfo("test", "Work 2 complete")
if __name__ == '__main__':
root = tk.Tk()
run_gui = SmallGui(root)
root.mainloop()
UPDATE:
For whatever reason my above solution works in python 3 but no in 2.7.14.
The below example however does work in 2.7.14 and should work for you.
What I have done here is create 2 class attributes to monitor each thread.
I have created a method that will check ever 1 second if a thread is active and if the thread becomes inactive a messagebox will popup.
from threading import Thread
from time import sleep
import Tkinter as tk
import tkMessageBox as mb
import ttk
class SmallGui(tk.Frame):
def __init__(self, master):
tk.Frame.__init__(self, master)
self.master = master
self.master.title('test gui')
self.button_1 = ttk.Button(self.master,
text='Start 1',
command=lambda: self.init_thread(1))
self.button_2 = ttk.Button(self.master,
text='Start 2',
command=lambda: self.init_thread(2))
self.button_1.pack()
self.button_2.pack()
self.work1_status = None
self.work2_status = None
def init_thread(self, work):
if work == 1:
self.work1_status = Thread(target=self.work_1)
self.work1_status.start()
self.check_thread(self.work1_status, work)
else:
self.work2_status = Thread(target=self.work_2)
self.work2_status.start()
self.check_thread(self.work2_status, work)
def check_thread(self, pass_thread, thread_name):
if pass_thread.isAlive() == False:
pass_thread = None
mb.showinfo("test", "Work {} complete".format(thread_name))
else:
self.after(1000, lambda: self.check_thread(pass_thread, thread_name))
@staticmethod
def work_1():
print ('Work 1 started')
# Do some Task and return a list
sleep(5)
@staticmethod
def work_2():
print ('Work 2 Started')
# Do some Task and return a list
sleep(5)
if __name__ == '__main__':
root = tk.Tk()
run_gui = SmallGui(root)
root.mainloop()
A easy solution to check thread finished or not. It is thread safe
Install pyrvsignal
pip install pyrvsignal
Example:
import time
from threading import Thread
from pyrvsignal import Signal
class MyThread(Thread):
started = Signal()
finished = Signal()
def __init__(self, target, args):
self.target = target
self.args = args
Thread.__init__(self)
def run(self) -> None:
self.started.emit()
self.target(*self.args)
self.finished.emit()
def do_my_work(details):
print(f"Doing work: {details}")
time.sleep(10)
def started_work():
print("Started work")
def finished_work():
print("Work finished")
thread = MyThread(target=do_my_work, args=("testing",))
thread.started.connect(started_work)
thread.finished.connect(finished_work)
thread.start()