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()
核心改动说明
- 把logger和Handler的初始化移到了App的初始化和build阶段,全局只创建一次,避免重复添加Handler导致重复日志。
- 新增
is_running标记,任务运行中点击按钮直接拦截,避免同时启动多个线程。 - 任务运行时把按钮置灰,直观提示用户当前不可点击,任务结束后恢复按钮状态。
- 给线程设置
daemon=True,保证APP关闭时线程会跟着一起退出,不会残留后台进程。 - 如果需要支持中途终止任务,可以新增一个
stop_flag标记,线程循环中每次判断该标记,需要终止时直接跳出循环即可。
内容的提问来源于stack exchange,提问作者xie186
相关产品推荐
相关产品推荐

