Search code examples
pythonkivypython-multithreadingkivy-languagekivymd

How to stop a thread after the task is finished in python?


In the code below, after I click the button for the first time, the spinner (MDSpinner) will start to spin and the logging.info() will be directed to the MDLabel. But after I click the button the second time, there will be two copies of logging.info() directed to MDLabel. How can I stop the thread after the task my_thread is finished? Thanks in advance.

import kivy
from kivy.lang import Builder
import logging
import threading
from kivy.uix.floatlayout import FloatLayout
from kivymd.app import MDApp
from kivy.clock import Clock

import time

root_kv = '''
BoxLayout:
    id: main
    orientation: "vertical"
    spacing: "20dp"
    padding: "50dp", "10dp", "50dp", "10dp"

    MDRaisedButton:
        id: button4read_mtx
        size_hint: None, None
        size: 4 * dp(48), dp(48)
        text: "Start"
        opposite_colors: True
        pos_hint: {"center_x": 0.5, "center_y": 0.5}
        on_release: app.analyze_data()
    MDSpinner:
        id: spinder4button
        size_hint: None, None
        size: dp(46), dp(46)
        pos_hint: {'center_x': .5, 'center_y': .5}
        active: False
    MDLabel:
        id: test_label_log
        text: "Log"
        halign: "left"
'''

exit_flag = 0
def my_thread(log, app):
    app.root.ids.spinder4button.active = True
    for i in range(5):
        time.sleep(1)
        log.info("Step %s", i)
    app.root.ids.spinder4button.active = False

class MDLabelHandler(logging.Handler):

    def __init__(self, label, level=logging.NOTSET):
        logging.Handler.__init__(self, level=level)
        self.label = label

    def emit(self, record):
        "using the Clock module for thread safety with kivy's main loop"
        def f(dt=None):
            self.label.text += "\n" + self.format(record)  #"use += to append..."
        Clock.schedule_once(f)


class MyApp(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        root = Builder.load_string(root_kv)
        return root
    def analyze_data(self):
        log = logging.getLogger("my.logger")
        log.level = logging.DEBUG
        log.addHandler(MDLabelHandler(self.root.ids.test_label_log, logging.DEBUG))
        #thread_info = _thread.start_new(my_thread, (log, self, ))
        #print(thread_info)
        x = threading.Thread(target=my_thread, args=(log, self, ))
        x.start()

if __name__ == '__main__':
    MyApp().run()

Solution

  • The thread is terminating, as it should, when the task completes. That is not the problem. The problem is that you are adding a new logging handler each time you run analyze_data(). The fix is to only add one handler:

    class MyApp(MDApp):
        def __init__(self, **kwargs):
            super().__init__(**kwargs)
            self.log = None
    
        def build(self):
            root = Builder.load_string(root_kv)
            return root
    
        def analyze_data(self):
            if self.log is None:
                self.log = logging.getLogger("my.logger")
                self.log.level = logging.DEBUG
                self.log.addHandler(MDLabelHandler(self.root.ids.test_label_log, logging.DEBUG))
            #thread_info = _thread.start_new(my_thread, (log, self, ))
            #print(thread_info)
            x = threading.Thread(target=my_thread, args=(self.log, self, ))
            x.start()