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

如何用Python Tkinter实现多同步倒计时定时器(同启动按钮)

解决Tkinter多定时器同步运行问题

你的代码里定时器顺序执行的核心原因是:time.sleep()会阻塞Tkinter的主事件循环,且lambda: [runTimer1(), runTimer2()]会先执行完runTimer1的整个循环,才会启动runTimer2。要实现同步运行,推荐使用Tkinter原生的after()方法,它不会阻塞主循环,能让多个定时器事件并行处理。

优化后的代码

import tkinter as tk
from tkinter import messagebox

root = tk.Tk()
root.geometry("500x500")
root.title("Countdown Timer")

# 定时器1的变量
hr1 = tk.StringVar(value="00")
min1 = tk.StringVar(value="00")
sec1 = tk.StringVar(value="00")

# 定时器2的变量
hr2 = tk.StringVar(value="00")
min2 = tk.StringVar(value="00")
sec2 = tk.StringVar(value="00")

# 创建输入框
tk.Entry(root, width=3, font=("Calibri", 20), textvariable=hr1).place(x=170, y=100)
tk.Entry(root, width=3, font=("Calibri", 20), textvariable=min1).place(x=220, y=100)
tk.Entry(root, width=3, font=("Calibri", 20), textvariable=sec1).place(x=270, y=100)

tk.Entry(root, width=3, font=("Calibri", 20), textvariable=hr2).place(x=170, y=180)
tk.Entry(root, width=3, font=("Calibri", 20), textvariable=min2).place(x=220, y=180)
tk.Entry(root, width=3, font=("Calibri", 20), textvariable=sec2).place(x=270, y=180)

def countdown(total_sec, hr_var, min_var, sec_var, finish_msg):
    if total_sec < 0:
        messagebox.showinfo("", finish_msg)
        return
    # 计算当前时分秒
    hours = total_sec // 3600
    minutes = (total_sec % 3600) // 60
    seconds = total_sec % 60
    # 更新UI显示
    hr_var.set(f"{hours:02d}")
    min_var.set(f"{minutes:02d}")
    sec_var.set(f"{seconds:02d}")
    # 1秒后递归调用自身,继续倒计时
    root.after(1000, countdown, total_sec - 1, hr_var, min_var, sec_var, finish_msg)

def start_timers():
    # 启动定时器1
    try:
        t1 = int(hr1.get())*3600 + int(min1.get())*60 + int(sec1.get())
        countdown(t1, hr1, min1, sec1, "time One has expired!")
    except ValueError:
        print("定时器1输入无效,请输入数字")
    
    # 启动定时器2
    try:
        t2 = int(hr2.get())*3600 + int(min2.get())*60 + int(sec2.get())
        countdown(t2, hr2, min2, sec2, "time Two has expired!")
    except ValueError:
        print("定时器2输入无效,请输入数字")

tk.Button(root, text='START', bd='5', command=start_timers).place(relx=0.5, rely=0.8, anchor=tk.CENTER)

root.mainloop()

关键改进点

  • 替换阻塞式循环:用after(1000, ...)实现非阻塞的延迟递归,每次只处理1秒的倒计时,不会卡住主事件循环,让两个定时器的更新事件能同时在主循环中排队执行。
  • 复用核心逻辑:把倒计时逻辑封装成通用的countdown函数,通过参数区分不同定时器的变量和结束提示,避免重复代码。
  • 精准异常捕获:明确捕获ValueError,输入无效时给出针对性提示。

备选方案:线程实现

如果需要处理复杂的后台任务,也可以用threading模块,但要注意Tkinter的UI操作必须在主线程执行,线程中不能直接修改StringVar,需通过root.after()触发UI更新:

import tkinter as tk
from tkinter import messagebox
import threading
import time

root = tk.Tk()
root.geometry("500x500")
root.title("Countdown Timer")

# 变量定义和输入框创建同上,省略重复代码

def run_timer(total_sec, hr_var, min_var, sec_var, finish_msg):
    def update_ui(h, m, s):
        hr_var.set(f"{h:02d}")
        min_var.set(f"{m:02d}")
        sec_var.set(f"{s:02d}")
    
    while total_sec >= 0:
        h = total_sec // 3600
        m = (total_sec % 3600) // 60
        s = total_sec % 60
        # 委托主线程更新UI
        root.after(0, update_ui, h, m, s)
        time.sleep(1)
        total_sec -= 1
    # 弹窗也需委托主线程执行
    root.after(0, messagebox.showinfo, "", finish_msg)

def start_timers():
    try:
        t1 = int(hr1.get())*3600 + int(min1.get())*60 + int(sec1.get())
        threading.Thread(target=run_timer, args=(t1, hr1, min1, sec1, "time One has expired!"), daemon=True).start()
    except ValueError:
        print("定时器1输入无效,请输入数字")
    
    try:
        t2 = int(hr2.get())*3600 + int(min2.get())*60 + int(sec2.get())
        threading.Thread(target=run_timer, args=(t2, hr2, min2, sec2, "time Two has expired!"), daemon=True).start()
    except ValueError:
        print("定时器2输入无效,请输入数字")

# 按钮和主循环同上

这种方法适合定时器需要执行复杂计算的场景,但线程模式需注意资源竞争问题,不如after()方法简洁安全。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 07:45:38