You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python如何在线程任务完成后停止,解决KivyMD日志重复输出问题

问题原因
  • 重复日志:每次点击按钮调用analyze_data方法时,都会给my.logger实例新增一个MDLabelHandler,点击多少次就会注册多少个Handler,每次打印日志时所有注册的Handler都会同时向Label写入内容,因此出现重复输出。
  • 线程无需额外手动停止:你当前写的my_thread执行完5次循环后,线程会自动销毁退出,无需额外的停止逻辑,你目前的问题是没有做重复点击拦截,会出现同时运行多个线程的情况,导致日志乱序、Spinner状态异常。
修复后的完整代码
import kivy
from kivy.lang import Builder
import logging
import threading
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"
'''

def my_thread(log, app):
    app.root.ids.spinder4button.active = True
    # 把按钮置灰避免重复点击
    Clock.schedule_once(lambda dt: setattr(app.root.ids.button4read_mtx, 'disabled', True))
    for i in range(5):
        # 如果需要支持中途终止,可以在这里判断app.stop_flag标记,符合条件就break
        time.sleep(1)
        log.info("Step %s", i)
    app.root.ids.spinder4button.active = False
    # 任务结束恢复按钮可用
    Clock.schedule_once(lambda dt: setattr(app.root.ids.button4read_mtx, 'disabled', False))
    # 重置运行标记
    app.is_running = False

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

    def emit(self, record):
        def f(dt=None):
            self.label.text += "\n" + self.format(record)
        Clock.schedule_once(f)

class MyApp(MDApp):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.is_running = False
        # 全局只初始化一次logger
        self.log = logging.getLogger("my.logger")
        self.log.setLevel(logging.DEBUG)

    def build(self):
        root = Builder.load_string(root_kv)
        # 只绑定一次Handler
        self.log.addHandler(MDLabelHandler(root.ids.test_label_log, logging.DEBUG))
        return root

    def analyze_data(self):
        # 任务运行中直接返回,避免重复启动
        if self.is_running:
            return
        self.is_running = True
        x = threading.Thread(target=my_thread, args=(self.log, self, ))
        # 设为守护线程,APP关闭时自动退出
        x.daemon = True
        x.start()

if __name__ == '__main__':
    MyApp().run()
核心改动说明
  1. 把logger和Handler的初始化移到了App的初始化和build阶段,全局只创建一次,避免重复添加Handler导致重复日志。
  2. 新增is_running标记,任务运行中点击按钮直接拦截,避免同时启动多个线程。
  3. 任务运行时把按钮置灰,直观提示用户当前不可点击,任务结束后恢复按钮状态。
  4. 给线程设置daemon=True,保证APP关闭时线程会跟着一起退出,不会残留后台进程。
  5. 如果需要支持中途终止任务,可以新增一个stop_flag标记,线程循环中每次判断该标记,需要终止时直接跳出循环即可。

内容的提问来源于stack exchange,提问作者xie186

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 02:57:00