使用线程和 tkinter 模块停止线程并避免 python 中的 'RuntimeError' 的最佳方法是什么?
What is the best way to stop a thread and avoid 'RuntimeError' in python using threading and tkinter modules?
我已经在网上浏览了多种解决方案,但它们需要大量代码,一旦您扩大规模,这些代码可能会让人感到困惑。有没有一种简单的方法来停止线程并避免 RuntimeError: threads can only be started once
,以便无限次调用线程。这是我的代码的一个简单版本:
import tkinter
import time
import threading
def func():
entry.config(state='disabled')
label.configure(text="Standby for seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
label.configure(text=str(sum))
entry.config(state='normal')
mainwindow = tkinter.Tk()
mainwindow.title('Sum up to any number')
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text = "Enter an integer",font=("Arial",33))
label.pack()
print(entry.get())
button = tkinter.Button(mainwindow, text="Press me", command=threading.Thread(target=func).start)
button.pack()
可以从其他线程调用对 tkinter 小部件的修改,并且它们会在主线程可用时立即发生,这可能会立即发生。如果调用修改的后台线程睡眠而主线程仅在 mainloop
,我们可以在应用程序中模拟暂停,而不会像问题所针对的那样阻塞在主线程上。
然后我们可以子类化 Thread
以生成一个线程,该线程 运行 有自己的循环并保持 started
即使在其目标完成后,我们也可以多次调用其目标如我们所愿。我们甚至可以通过使用 daemon
模式和 try
-except
块传递后台线程上发生的错误并优雅地退出线程而不挂起应用程序。
BooleanVar
thread.do
作为一个开关,我们可以在 lambda
到 运行 func
中设置一次 thread
当按下 button
时。这在主线程和后台线程之间实现了一个廉价的消息传递系统,我们可以用很少的额外代码对其进行扩展,以允许使用参数调用 func
并从中返回值。
import threading, time, tkinter, sys
class ImmortalThread(threading.Thread):
def __init__(self, func):
super().__init__(daemon=True)
self.func = func
self.do = tkinter.BooleanVar()
def run(self):
while True:
if self.do.get():
try:
self.func()
self.do.set(False)
except:
print("Exception on", self, ":", sys.exc_info())
raise SystemExit()
else:
# keeps the thread running no-ops so it doesn't strip
time.sleep(0.01)
def func():
entry.config(state='disabled')
label.configure(text="Standby for seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
label.configure(text=str(sum))
entry.config(state='normal')
mainwindow = tkinter.Tk()
mainwindow.title("Sum up to any number")
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text="Enter an integer", font=("Arial", 33))
label.pack()
thread = ImmortalThread(func)
thread.start()
button = tkinter.Button(mainwindow, text="Press me", command=lambda: thread.do.set(True))
button.pack()
mainwindow.mainloop()
虽然这是一种完成任务的简单方法,但代码行更少。我无法使用 .join()
方法。但是,该应用程序似乎没有创建任何新线程。这通过 threading.active_counts()
方法是显而易见的。这是下面的代码:
import tkinter, threading, time
def calc():
entry.config(state='disabled')
label.configure(text="Standby for 3 seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
labelnum.configure(text=str(sum))
button.config(state='normal')
label.configure(text="Sum up to any number")
entry.config(state='normal')
def func():
t = threading.Thread(target=calc)
t.start()
#del t
print('Active threads:',threading.active_count())
mainwindow = tkinter.Tk()
mainwindow.title('Sum up to any number')
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text = "Enter an integer",font=("Arial",33))
label.pack()
labelnum = tkinter.Label(mainwindow, text="",font=('Arial',33))
labelnum.pack()
button = tkinter.Button(mainwindow, text="Press me", command=func)
button.pack()
mainwindow.mainloop()
我已经在网上浏览了多种解决方案,但它们需要大量代码,一旦您扩大规模,这些代码可能会让人感到困惑。有没有一种简单的方法来停止线程并避免 RuntimeError: threads can only be started once
,以便无限次调用线程。这是我的代码的一个简单版本:
import tkinter
import time
import threading
def func():
entry.config(state='disabled')
label.configure(text="Standby for seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
label.configure(text=str(sum))
entry.config(state='normal')
mainwindow = tkinter.Tk()
mainwindow.title('Sum up to any number')
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text = "Enter an integer",font=("Arial",33))
label.pack()
print(entry.get())
button = tkinter.Button(mainwindow, text="Press me", command=threading.Thread(target=func).start)
button.pack()
可以从其他线程调用对 tkinter 小部件的修改,并且它们会在主线程可用时立即发生,这可能会立即发生。如果调用修改的后台线程睡眠而主线程仅在 mainloop
,我们可以在应用程序中模拟暂停,而不会像问题所针对的那样阻塞在主线程上。
然后我们可以子类化 Thread
以生成一个线程,该线程 运行 有自己的循环并保持 started
即使在其目标完成后,我们也可以多次调用其目标如我们所愿。我们甚至可以通过使用 daemon
模式和 try
-except
块传递后台线程上发生的错误并优雅地退出线程而不挂起应用程序。
BooleanVar
thread.do
作为一个开关,我们可以在 lambda
到 运行 func
中设置一次 thread
当按下 button
时。这在主线程和后台线程之间实现了一个廉价的消息传递系统,我们可以用很少的额外代码对其进行扩展,以允许使用参数调用 func
并从中返回值。
import threading, time, tkinter, sys
class ImmortalThread(threading.Thread):
def __init__(self, func):
super().__init__(daemon=True)
self.func = func
self.do = tkinter.BooleanVar()
def run(self):
while True:
if self.do.get():
try:
self.func()
self.do.set(False)
except:
print("Exception on", self, ":", sys.exc_info())
raise SystemExit()
else:
# keeps the thread running no-ops so it doesn't strip
time.sleep(0.01)
def func():
entry.config(state='disabled')
label.configure(text="Standby for seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
label.configure(text=str(sum))
entry.config(state='normal')
mainwindow = tkinter.Tk()
mainwindow.title("Sum up to any number")
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text="Enter an integer", font=("Arial", 33))
label.pack()
thread = ImmortalThread(func)
thread.start()
button = tkinter.Button(mainwindow, text="Press me", command=lambda: thread.do.set(True))
button.pack()
mainwindow.mainloop()
虽然这是一种完成任务的简单方法,但代码行更少。我无法使用 .join()
方法。但是,该应用程序似乎没有创建任何新线程。这通过 threading.active_counts()
方法是显而易见的。这是下面的代码:
import tkinter, threading, time
def calc():
entry.config(state='disabled')
label.configure(text="Standby for 3 seconds")
time.sleep(3)
sum = 0
for i in range(int(entry.get())):
time.sleep(0.5)
sum = sum+i
labelnum.configure(text=str(sum))
button.config(state='normal')
label.configure(text="Sum up to any number")
entry.config(state='normal')
def func():
t = threading.Thread(target=calc)
t.start()
#del t
print('Active threads:',threading.active_count())
mainwindow = tkinter.Tk()
mainwindow.title('Sum up to any number')
entry = tkinter.Entry(mainwindow)
entry.pack()
label = tkinter.Label(mainwindow, text = "Enter an integer",font=("Arial",33))
label.pack()
labelnum = tkinter.Label(mainwindow, text="",font=('Arial',33))
labelnum.pack()
button = tkinter.Button(mainwindow, text="Press me", command=func)
button.pack()
mainwindow.mainloop()