使用线程和 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()