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()
Asked By: user9368716

||

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()
Answered By: Mike – SMT

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()
Answered By: Ravikirana B
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.