wim*_*rks 5 python multithreading tkinter python-3.x
我正在 python 中使用 tkinter 制作一个计时器。该小部件只有一个按钮。该按钮兼作显示剩余时间的元素。计时器有一个线程,可以简单地更新按钮上显示的时间。
该线程仅使用一个 while 循环,该循环应在设置事件时停止。当窗口关闭时,我使用协议调用设置此事件的函数,然后尝试加入线程。这在大多数情况下都有效。但是,如果我在进行某个调用时关闭程序,则会失败并且线程在窗口关闭后继续运行。
我知道有关关闭 tkinter 窗口时关闭线程的其他 类似线程。但这些答案已经过时了,如果可能的话,我想避免使用 thread.stop() 。
我尝试尽可能减少这一点,同时仍然表明我对该计划的意图。
import tkinter as tk
from tkinter import TclError, ttk
from datetime import timedelta
import time
import threading
from threading import Event
def strfdelta(tdelta):
# Includes microseconds
hours, rem = divmod(tdelta.seconds, 3600)
minutes, seconds = divmod(rem, 60)
return str(hours).rjust(2, '0') + ":" + str(minutes).rjust(2, '0') + \
":" + str(seconds).rjust(2, '0') + ":" + str(tdelta.microseconds).rjust(6, '0')[0:2]
class App(tk.Tk):
def __init__(self):
super().__init__()
self.is_running = False
is_closing = Event()
self.start_time = timedelta(seconds=4, microseconds=10, minutes=0, hours=0)
self.current_start_time = self.start_time
self.time_of_last_pause = time.time()
self.time_of_last_unpause = None
# region guisetup
self.time_display = None
self.geometry("320x110")
self.title('Replace')
self.resizable(False, False)
box1 = self.create_top_box(self)
box1.place(x=0, y=0)
# endregion guisetup
self.timer_thread = threading.Thread(target=self.timer_run_loop, args=(is_closing, ))
self.timer_thread.start()
def on_close(): # This occasionally fails when we try to close.
is_closing.set() # This used to be a boolean property self.is_closing. Making it an event didn't help.
print("on_close()")
try:
self.timer_thread.join(timeout=2)
finally:
if self.timer_thread.is_alive():
self.timer_thread.join(timeout=2)
if self.timer_thread.is_alive():
print("timer thread is still alive again..")
else:
print("timer thread is finally finished")
else:
print("timer thread finished2")
self.destroy() # /sf/ask/7780881/
self.protocol("WM_DELETE_WINDOW", on_close)
def create_top_box(self, container):
box = tk.Frame(container, height=110, width=320)
box_m = tk.Frame(box, bg="blue", width=320, height=110)
box_m.place(x=0, y=0)
self.time_display = tk.Button(box_m, text=strfdelta(self.start_time), command=self.toggle_timer_state)
self.time_display.place(x=25, y=20)
return box
def update_shown_time(self, time_to_show: timedelta = None):
print("timer_run_loop must finish. flag 0015") # If the window closes at this point, everything freezes
self.time_display.configure(text=strfdelta(time_to_show))
print("timer_run_loop must finish. flag 016")
def toggle_timer_state(self):
# update time_of_last_unpause if it has never been set
if not self.is_running and self.time_of_last_unpause is None:
self.time_of_last_unpause = time.time()
if self.is_running:
self.pause_timer()
else:
self.start_timer_running()
def pause_timer(self):
pass # Uses self.time_of_last_unpause, Alters self.is_running, self.time_of_last_pause, self.current_start_time
def timer_run_loop(self, event):
while not event.is_set():
if not self.is_running:
print("timer_run_loop must finish. flag 008")
self.update_shown_time(self.current_start_time)
print("timer_run_loop must finish. flag 018")
print("timer_run_loop() ending")
def start_timer_running(self):
pass # Uses self.current_start_time; Alters self.is_running, self.time_of_last_unpause
if __name__ == "__main__":
app = App()
app.mainloop()
Run Code Online (Sandbox Code Playgroud)
你甚至不必按下按钮,这个错误就会显现出来,但它确实需要尝试和错误。我只是运行它并按 alt f4 直到它发生。
如果您运行此程序并遇到问题,您将看到“timer_run_loop必须完成。flag 0015”是我们检查线程是否结束之前打印的最后一个内容。也就是说,
self.time_display.configure(text=strfdelta(time_to_show))
还没有结束。我认为当线程在其中使用此 tkinter 按钮时关闭 tkinter 窗口会以某种方式导致问题。
关于 tkinter 中的配置方法似乎很少有可靠的文档。Python 的tkinter 官方文档只是顺便提到了该函数。它只是用作只读字典。
tkinter 样式类获取有关其配置方法的一些详细信息,但这没有帮助。
tkdocs将配置(又名 config)列为可用于所有小部件的方法之一。
本教程文章似乎是唯一展示实际使用的功能的地方。但它没有提及该方法可能遇到的任何可能的问题或异常。
是否有一些我没有使用的资源共享模式?或者有更好的方法来结束这个线程吗?
好的,所以,首先我想介绍一下.after
方法,它可以与你的widget结合使用,不需要使用线程
请注意,该函数被调用一次并再次调用自身,使得循环通常update_time
不会干扰 的主循环。tkinter
这将与程序一起关闭,没有任何问题。
import datetime
from tkinter import *
start_time = datetime.datetime.now()
def update_timer():
current_time = datetime.datetime.now()
timer_label.config(text=f'{current_time - start_time}')
root.after(1000, update_timer)
root = Tk()
timer_label = Label(text='0')
timer_label.pack()
update_timer()
root.mainloop()
Run Code Online (Sandbox Code Playgroud)
现在关于非守护线程的一些解释...当您创建一个非守护线程时,它会一直运行直到执行完毕,也就是说,即使parent
关闭它也会保持打开状态,直到进程结束。
# import module
from threading import *
import time
# creating a function
def thread_1():
for i in range(5):
print('this is non-daemon thread')
time.sleep(2)
# creating a thread T
T = Thread(target=thread_1)
# starting of thread T
T.start()
# main thread stop execution till 5 sec.
time.sleep(5)
print('main Thread execution')
Run Code Online (Sandbox Code Playgroud)
输出
this is non-daemon thread
this is non-daemon thread
this is non-daemon thread
main Thread execution
this is non-daemon thread
this is non-daemon thread
Run Code Online (Sandbox Code Playgroud)
现在看使用守护线程的相同示例,该线程将尊重主线程的执行性质,也就是说,如果它停止,“子线程”也会停止。
this is non-daemon thread
this is non-daemon thread
this is non-daemon thread
main Thread execution
this is non-daemon thread
this is non-daemon thread
Run Code Online (Sandbox Code Playgroud)
this is thread T
this is thread T
this is Main Thread
Run Code Online (Sandbox Code Playgroud)
也就是说,我的主要解决方案是使用.after
,如果即使这个解决方案失败并且您需要使用线程,使用线程daemon=True
,这将在您关闭应用程序后正确关闭线程tkinter
归档时间: |
|
查看次数: |
550 次 |
最近记录: |