0

I am creating a gui tool by python. Here comes a question that when I triggered a sub-thread to run some logic code by clicking on a button.The processing time maybe very long and some error maybe occurred during the process.And if error occurs I will send a error event to nofify the EventManager and call some function to kill/exit the sub-thread because its non-sense to continully run the left logic code. But I dont know how to kill/exit/stop the sub-thread properly using the error event.Could somebody help me plz?

from queue import Queue, Empty
from threading import *
from tkinter import *
import time
from tkinter import ttk

EVENT_TYPE_1 = "Count"
EVENT_TYPE_2 = "Error"
MAX_NUMBER = 10
CUR_NUMBER = 0


class event_manager:
    def __init__(self):
        self._eventQueue = Queue()
        self._thread = Thread(target=self.Run, daemon=True)
        self._handlers = {}
        self._active = False

    def Start(self):
        self._active = True
        self._thread.start()

    def Run(self):
        while self._active is True:
            try:
                event = self._eventQueue.get(block=True, timeout=1)
                self.Process(event)
            except Empty:
                pass

    def Process(self, event):
        if event.type in self._handlers:
            for handler in self._handlers[event.type]:
                handler()
        else:
            pass

    def Stop(self):
        self._active = False
        self._thread.join()

    def addEventListenter(self, type_, handler):
        try:
            handlerList = self._handlers[type_]

        except KeyError:
            handlerList = []
            self._handlers[type_] = handlerList

        if handler not in handlerList:
            handlerList.append(handler)

    def removeEventListenter(self, type_, handler):
        try:
            handlerList = self._handlers[type_]
            if handler in handlerList:
                handlerList.remove(handler)
            if not handlerList:
                del self._handlers[type_]
        except KeyError:
            pass

    def sendEvent(self, event):
        self._eventQueue.put(event)


class Event:
    def __init__(self, event_event_name, cur_done_task, type_=None):
        self.type = type_
        self._event_name = event_event_name
        self._curDoneTask = cur_done_task


class EventSource:
    def __init__(self, event_name, event_mgr, max_number, type):
        self._event_name = event_name
        self._event_manager = event_mgr
        self._type = type
        self._max_number = max_number

    def count(self):
        global CUR_NUMBER
        for i in range(self._max_number):
            CUR_NUMBER = i + 1

            if CUR_NUMBER == 4:  # assume this is a error check function,if error occurs,it will send a error event,and hopefully this event can terminate the sub-thread which is exactly running current code 
                print("************ detect error occurred , this thread should be terminated immediately !")
                errorEvent = Event("error", CUR_NUMBER, type_=EVENT_TYPE_2)
                self._event_manager.sendEvent(errorEvent)

            print(
                "************ main thread start:now start process {} - count : {}".format(self._event_name, CUR_NUMBER))
            event = Event("test", CUR_NUMBER, type_=self._type)
            self._event_manager.sendEvent(event)
            time.sleep(1)


class GUIListener(Tk):
    def __init__(self):
        super(GUIListener, self).__init__()

        self.title("Progress GUI")
        self.geometry("1200x805+600+100")
        self.config(bg="#535353")
        self.resizable(True, True)
        self.taskThread = None

        self.progressBar = ttk.Progressbar(master=self, orient=HORIZONTAL, maximum=MAX_NUMBER, length=300)
        self.progressBar.pack()
        self.button = ttk.Button(self, text="Run", command=lambda: self.button_function(MAX_NUMBER))
        self.button.pack()

    def update_progress_value(self):
        print("************Sub thread start: detect progress bar value is now...{}".format(self.progressBar['value']))
        self.progressBar['value'] = CUR_NUMBER
        self.progressBar.update_idletasks()
        print("************Sub thread start: update progress bar value to...{}".format(CUR_NUMBER))

    def button_function(self, max_number):
        # though I can make some error check function before task thread starts,but I really want to discuss the way that using event-system here 
        es = EventSource("eventSource", eventMgr, max_number, EVENT_TYPE_1)
        self.taskThread = Thread(target=es.count, daemon=True).start()  # here to start the sub-thread which need to be terminated by error event

    def terminate_error_thread(self):  # this method will be called when GUIListener recieved error event and terminate the sub-thread immediately
        pass
        # TODO: but how to implement this method?


if __name__ == '__main__':
    gui = GUIListener()

    eventMgr = event_manager()
    eventMgr.addEventListenter(EVENT_TYPE_1, gui.update_progress_value)
    eventMgr.addEventListenter(EVENT_TYPE_2, gui.terminate_error_thread)

    eventMgr.Start()

    gui.mainloop()

Hopefully the console will give me the logs as following:

************ main thread start:now start process eventSource - count : 1
************Sub thread start: detect progress bar value is now...0.0
************Sub thread start: update progress bar value to...1
************ main thread start:now start process eventSource - count : 2
************Sub thread start: detect progress bar value is now...1
************Sub thread start: update progress bar value to...2
************ main thread start:now start process eventSource - count : 3
************Sub thread start: detect progress bar value is now...2
************Sub thread start: update progress bar value to...3
************ detect error occurred , this thread should be terminated immediately !

1 Answers1

0

In the count function, you could simply break out of the loop when you want to exit. If the function finishes running, the thread will exit automatically.

For example,

class EventSource:
    def __init__(self, event_name, event_mgr, max_number, type):
        self._event_name = event_name
        self._event_manager = event_mgr
        self._type = type
        self._max_number = max_number

    def count(self):
        global CUR_NUMBER
        for i in range(self._max_number):
            CUR_NUMBER = i + 1

            if CUR_NUMBER == 4:  # assume this is a error check function,if error occurs,it will send a error event,and hopefully this event can terminate the sub-thread which is exactly running current code 
                print("************ detect error occurred , this thread should be terminated immediately !")
                errorEvent = Event("error", CUR_NUMBER, type_=EVENT_TYPE_2)
                # self._event_manager.sendEvent(errorEvent)
                break

            print("************ main thread start:now start process {} - count : {}".format(self._event_name, CUR_NUMBER))
            event = Event("test", CUR_NUMBER, type_=self._type)
            self._event_manager.sendEvent(event)
            time.sleep(1)
Tim
  • 3,178
  • 1
  • 13
  • 26
  • thx,bro.this does can solve my problem in the sample code,but actully I could have dozens of sub-thread to run different logic code,and each of the logic's error condition are not the same.So the ideal ways is to use event system ---- when the error event has been sent to event manager,it will calls a universal method to kill the current sub thread.I am wondering how to implement in this way – lezard seventhbible Feb 23 '21 at 02:52
  • @lezardseventhbible in general is bad to kill a thread externally see https://stackoverflow.com/questions/323972/is-there-any-way-to-kill-a-thread, you could implement something like stoppable thread like in the link. But IMO, you don’t need the fancy stuff. Just have the thread exit the function (e.g. via return) when certain conditions are met achieves exactly the same thing. You can still send stuff to the event manager before the return if desired. – Tim Feb 23 '21 at 03:41