Как правильно сделать посекундный счетчик для разных потоков в Python, используя tkinter?
Я пытаюсь создать приложение, которое при нажатии кнопки "Start" запускало бы новый поток. Для каждого потока ведется посекундный счет о времени его работы в реальном времени.
К сожалению, счетчик работает некорректно: с каждым новым запущенным потоком он увеличивается не на одну единицу, а на 2, 3, 4 и так далее.
Чем больше потоков запущено, тем больше увеличивается сам счетчик.
Например, если запущено десять потоков, счетчик каждую секунду увеличивается на 10 единиц, а не на одну, как нужно.
Подозреваю, что это как-то связано с рекурсией в коде, но не могу понять, как решить проблему?
Благодарю всех, кто откликнется.
import threading
import time
import tkinter as tk
from threading import Thread
from datetime import datetime
from time import time
threads = []
temp = 0
thread_number = 0
after_id = ""
def thread_start():
global thread_number
thread_number += 1
new_thread = Thread(target=thread_func, args=(thread_number,))
threads.append(new_thread)
new_thread.start()
def thread_func(thread_number):
temp = 0
def tick():
global temp, after_id
label = tk.Label(win, text="Hello")
label.grid(row=0+thread_number, column=0)
after_id = win.after(1000, tick)
label.configure(text=str(temp))
temp += 1
tick()
win = tk.Tk()
win.title("Threads counter example")
win.geometry("800x600")
tk.Button(win, text='Start', command=thread_start,).grid(row=0,column=1,stick='we')
win.mainloop()
Ответы (2 шт):
Попробуйте так:
import threading
# ?import time
import tkinter as tk
from threading import Thread
# ?from datetime import datetime
# ?from time import time
threads = [] # ?
#temp = 0 # ?
thread_number = 0
after_id = "" # ?
labels = [] # <---- !!!
def thread_start():
global thread_number
# thread_number += 1
new_thread = Thread(target=thread_func, args=(thread_number,))
threads.append(new_thread)
new_thread.start()
thread_number += 1 # +++
def thread_func(thread_number):
#print(f'\nthread_number={thread_number}') #
temp = 0
label = tk.Label(win, text="Hello") # +++
label.grid(row=1+thread_number, column=0) # +++
labels.append([label, temp]) # <---- !!!
def tick():
labels[thread_number][0].configure( # <---- !!!
text=str(labels[thread_number][1])) # <---- !!!
labels[thread_number][1] += 1 # <---- !!!
after_id = win.after(1000, tick)
tick()
win = tk.Tk()
win.title("Threads counter example")
win.geometry("200x400+400+100")
tk.Button(win, text='Start', command=thread_start,).grid(row=0,column=1,stick='we')
win.mainloop()
Вы скрестили два подхода: планирование выполнения через after и потоки. Если уж используете потоки, то можно обойтись без after (использовать обычные while True и time.sleep()). Если нет какого-то долгого блокирующего поток кода, то можно обойтись без потоков, и использовать просто after. Скрещивание этих двух вариантов возможно, но усложняет код.
Пример через потоки (код немного упростил, добавил daemon=True, чтобы потоки автоматически завершались при закрытии окна):
import time
import tkinter as tk
from threading import Thread
import time
threads = []
def thread_start():
thread_number = len(threads) + 1
new_thread = Thread(target=thread_func, args=(thread_number,), daemon=True)
threads.append(new_thread)
new_thread.start()
def thread_func(thread_number):
temp = 0
label = tk.Label(win, text="Hello")
label.grid(row=0+thread_number, column=0)
while True:
label.configure(text=str(temp))
temp += 1
time.sleep(1)
win = tk.Tk()
win.title("Threads counter example")
win.geometry("800x600")
tk.Button(win, text='Start', command=thread_start).grid(row=0, column=0, stick='we')
win.mainloop()
Через after без потоков:
import tkinter as tk
count = 0
def start():
global count
count += 1
func(count)
def func(count):
label = tk.Label(win, text="Hello")
label.grid(row=0 + count, column=0)
def tick(temp=0):
label.configure(text=str(temp))
temp += 1
win.after(1000, tick, temp)
tick()
win = tk.Tk()
win.title("Threads counter example")
win.geometry("800x600")
tk.Button(win, text='Start', command=start).grid(row=0, column=0, stick='we')
win.mainloop()
