What is the best way to stop a thread and avoid 'RuntimeError' in python using threading and tkinter modules?

泪湿孤枕 提交于 2020-06-27 08:16:20

问题


I have went through multiple solutions on the net, but they require a lot of code that might get confusing once you scale up. Is there a simple way to stop the thread and avoid the RuntimeError: threads can only be started once, in order to call the thread an infinite number of times. Here is a simple version of my code:

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()

回答1:


It is possible to call modifications on tkinter widgets from other threads, and they will occur as soon as the main thread is available, which may be immediately. If the background thread calling the modifications sleeps while the main thread is only in mainloop, we can simulate a pause in the app without blocking on the main thread as the question aims for.

Then we can subclass Thread to produce a thread that runs its own loop and remains started even after its target finishes so that we can call its target as many times as we like. We can even pass errors that occur on the background thread through and gracefully exit the thread without hanging the app by using daemon mode and a try-except block.

The BooleanVar thread.do acts as a switch that we can set in a lambda to run func once on the thread when button is pressed. This implements a cheap messaging system between the main and background threads which we could extend with little extra code to allow calling func with arguments and returning values from it.

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()



回答2:


While this is a simple way that get things done, in a fewer lines of code. I couldn't use the .join() method. But, it seems that the app isn't creating any new threads. This is obvious through threading.active_counts() method. Here is the code below:

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()


来源:https://stackoverflow.com/questions/62351129/what-is-the-best-way-to-stop-a-thread-and-avoid-runtimeerror-in-python-using-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!