Warning: file_get_contents(/data/phpspider/zhask/data//catemap/5/excel/23.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 如何使用线程来创建多个独立的可重用对象实例?_Python_Multithreading_Timer_Tkinter - Fatal编程技术网

Python 如何使用线程来创建多个独立的可重用对象实例?

Python 如何使用线程来创建多个独立的可重用对象实例?,python,multithreading,timer,tkinter,Python,Multithreading,Timer,Tkinter,我正在用Python构建一个GUI,并使用线程生成多个独立工作的倒计时计时器。我有一个最小化的模块,用于测试,包括下面的模块。我希望按钮开始倒计时,然后再次单击时停止,然后重置,以便下次单击时可以开始。一旦时间用完,也会自行重置。唯一的问题是,在它停止后,我无法让它重新启动。我得到“无法启动线程两次”错误。我一直在尝试使用一个条件循环来让线程退出其自身,但它一直没有工作。我真的很想了解一些情况 实际上,我希望这个程序能够做两件事。 1) 一直运行计时器,然后自动重置,以便重新启动 2)停止在倒计

我正在用Python构建一个GUI,并使用线程生成多个独立工作的倒计时计时器。我有一个最小化的模块,用于测试,包括下面的模块。我希望按钮开始倒计时,然后再次单击时停止,然后重置,以便下次单击时可以开始。一旦时间用完,也会自行重置。唯一的问题是,在它停止后,我无法让它重新启动。我得到“无法启动线程两次”错误。我一直在尝试使用一个条件循环来让线程退出其自身,但它一直没有工作。我真的很想了解一些情况

实际上,我希望这个程序能够做两件事。 1) 一直运行计时器,然后自动重置,以便重新启动 2)停止在倒计时的中间,使其自动复位,以便它可以重新启动

我认为解决这些问题对社区来说是很有价值的,因为这是一个现实世界中解决问题的例子,很多人在论坛上谈论这个问题,即如何绕过禁止重启线程

__author__ = 'iKRUSTY'

'''
there are two things i would like this code to be able to do
1) run all the way through the timer and then reset so that it can be restarted
2) be stopped before completing and then reset so that it can be restarted

'''

from tkinter import *
import time
import os
import threading

#Variables
global FRYER_ONE_TIME_VAR       # holds the value for changing label text to update timer
global BASKET_ONE_TARGET_TIME       #this is a user input that will determine the length of the countdown
global timerOneStayAlive        #this is the value that i am attempting to use so that the thread closes after it is set to false
timerOneStayAlive = FALSE       #initializes to false because the the invert function changes it to true once the button is clicked

FRYER_ONE_TIME_VAR=" "        #used to pass time between functiuons

#Font Profiles
SMALLEST_FONT = ("Verdana", 9)
SMALL_FONT = ("Verdana", 10)
LARGE_FONT = ("Verdana", 12)
LARGEST_FONT = ("Verdana", 18)

class timer():
    global BASKET_ONE_TARGET_TIME
    BASKET_ONE_TARGET_TIME = 5      #Just setting it manually for now

    def __init__(self):
        self.s = 0      #these values are used to convert from seconds to a minute:second format
        self.m = 0       #these values are used to convert from seconds to a minute:second format

    def SetTime(self, seconds):
        self.seconds=seconds        #this is a counter that will be used to calculate remaining time

    def TimerReset(self):
        self.seconds = BASKET_ONE_TARGET_TIME       #resets counter to target time

    def StartCountdown(self, FryerLabel): #takes a label as an argumet to tell it where to display the countdown

        global timerOneStayAlive
        print("StartCountdown Started!")        #USED FOR TROUBLE SHOOTING
        self.seconds = BASKET_ONE_TARGET_TIME   #set start value for seconds counter
        self.seconds=self.seconds+1      #makes the full time appear upon countdown start

        while self.seconds > 0:
            FRYER_ONE_TIME_VAR = self.CalculateTime()       #Calculate time reduces the counter by one and reformats it to a minute:second format. returns a string
            FryerLabel.config(text=FRYER_ONE_TIME_VAR)      #Update Label with current value
            print(self.seconds)                               #USED FOR TROUBLE SHOOTING
            time.sleep(1)
            # reset label with default time
            if self.seconds == 0:                           #Reset once counter hits zero
                print("resetting time")                       #USED FOR TROUBLE SHOOTING
                self.seconds = BASKET_ONE_TARGET_TIME + 1
                FRYER_ONE_TIME_VAR = self.CalculateTime()
                FryerLabel.config(text=FRYER_ONE_TIME_VAR)
                break
        print("TimerStayAlive before invert: ", timerOneStayAlive)          #USED FOR TROUBLE SHOOTING
        timerOneStayAlive = invert(timerOneStayAlive)                   #inverts the value back to FALSE so that Ideally the loop breaks
                                                                        #  and the thread completes so that it can be called again
        print("TimerStayAlive after invert: ", timerOneStayAlive)       #USED FOR TROUBLE SHOOTING
        print("end startcountdown")                                 #USED FOR TROUBLE SHOOTING

    def CalculateTime(self):
        #print("CalculateTime has been called")
        lessThanTen=0
        self.seconds = self.seconds - 1
        self.m, self.s = divmod(self.seconds, 60)

        if self.s<10:
            lessThanTen=1
        #create time String Variables
        colonVar=':'
        minutesString = str(self.m)
        secondsString = str(self.s)
        #insert variables into string array
        timeArray = []
        timeArray.append(minutesString)
        timeArray.append(colonVar)
        if lessThanTen == 1:
            timeArray.append("0")
        timeArray.append(secondsString)
        #prepare for output
        self.timeString = ''.join(timeArray)
        return self.timeString

def invert(boolean):
    return not boolean

def BasketOneButtonClicked():
    print("button clicked")             #USED FOR TROUBLE SHOOTING
    global timerOneStayAlive
    timerOneStayAlive = invert(timerOneStayAlive)   #Changes from FALSE to TRUE
    if timerOneStayAlive == TRUE:
        print("timerOneStayAlive: ", timerOneStayAlive)         #USED FOR TROUBLE SHOOTING
        basketOneThread.start()
        updateButtonStatus()                                #changes text of button depending on whether timerOneStayAlive is TRUE or FALSE
    else:
        print("timerOneStayAlive: ", timerOneStayAlive)     #USED FOR TROUBLE SHOOTING
        return


def updateButtonStatus():
    global timerOneStayAlive
    if timerOneStayAlive == FALSE:
        basketOneStartButton.config(text="Start")
    if timerOneStayAlive == TRUE:
        basketOneStartButton.config(text="Stop")


def BasketOneThreadComShell():          # I used this so that i can ideally call multiple functions with a single thread
    '''
    This is where i think the problem may be. this is what is called when the thread is initialized and theoretically
    when this completes the thread should come to a halt so that when the button is reset, the thread can be called again
    I think that the function is completing but for some reason the thread keeps on running.
    '''

    global timerOneStayAlive
    print("ComShell Started")       #USED FOR TROUBLE SHOOTING
    while timerOneStayAlive:
        basketOneTimer.StartCountdown(countdownLabelBasket1)
        updateButtonStatus()
        if timerOneStayAlive == FALSE:      #redundant because while loop should do it. i just tried it because i couldnt get the process to end
            break
    print("Threadshell has ended")      #USED FOR TROUBLE SHOOTING
    return
    print("after return check")     #USED FOR TROUBLE SHOOTING



root = Tk()

'''
the  following is all just building the GUI Stuff
'''

Container = Frame(root)
Container.grid(row=1, column=0, padx=10, pady=10)
countdownContainerBasket1 = Label(Container, width=10, height=5)
countdownContainerBasket1.grid(row=2, column=0, sticky=NSEW)
countdownLabelBasket1 = Label(countdownContainerBasket1, text="Basket 1", background="white", anchor=CENTER, width=10, height=6, font=LARGE_FONT, padx=20)
countdownLabelBasket1.pack()
basketOneTimer = timer()
basketOneTimer.SetTime(5)
basketOneStartButton = Button(Container, text="Start", font=LARGE_FONT, command=BasketOneButtonClicked)
basketOneStartButton.grid(row=3, column=0, padx=10, pady=10)

basketOneThread = threading.Thread(target=BasketOneThreadComShell) #this is where the thread is initialized. start() is called in BasketOneButtonClick

print(threading.active_count)       #tried to use this to see if the thread was exiting but it didnt help

root.mainloop()
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
'''
有两件事我希望这段代码能够做到
1) 一直运行计时器,然后重置,以便可以重新启动
2) 在完成之前停止,然后重置,以便重新启动
'''
从tkinter进口*
导入时间
导入操作系统
导入线程
#变数
全局FRYER_ONE_TIME_VAR#保存更改标签文本以更新计时器的值
全局篮_ONE_TARGET_TIME#这是一个用户输入,将决定倒计时的长度
全局timerOneStayAlive#这是我试图使用的值,以便线程在设置为false后关闭
timerOneStayAlive=FALSE#初始化为FALSE,因为单击按钮后反转函数会将其更改为true
FRYER_ONE_TIME_VAR=”“#用于在函数之间传递时间
#字体配置文件
最小字体=(“Verdana”,9)
小字体=(“Verdana”,10)
大字体=(“Verdana”,12)
最大字体=(“Verdana”,18)
类计时器():
全球篮子(1)目标(1)时间
篮子(一个)目标(目标)时间=5#目前只需手动设置即可
定义初始化(自):
self.s=0#这些值用于从秒转换为分:秒格式
self.m=0#这些值用于从秒转换为分:秒格式
def设置时间(自身,秒):
self.seconds=seconds#这是一个用于计算剩余时间的计数器
def定时器设置(自身):
self.seconds=BASKET_ONE_TARGET_TIME#将计数器重置为目标时间
def StartCountdown(自我,FryerLabel):#将标签作为参数,告诉它在哪里显示倒计时
全球时间序列
打印(“开始计数!”)#用于故障排除
self.seconds=BASKET_ONE_TARGET_TIME#设置秒计数器的开始值
self.seconds=self.seconds+1#在倒计时开始时显示完整时间
当self.seconds>0时:
FRYER_ONE_TIME_VAR=self.CalculateTime()#计算时间将计数器减少1,并将其重新格式化为一分钟:秒格式。返回一个字符串
FryerLabel.config(text=FRYER_ONE_TIME_VAR)#使用当前值更新标签
打印(self.seconds)#用于故障排除
时间。睡眠(1)
#使用默认时间重置标签
如果self.seconds==0:#计数器为零时重置
打印(“重置时间”)#用于故障排除
self.seconds=BASKET\u ONE\u TARGET\u TIME+1
FRYER\u ONE\u TIME\u VAR=self.CalculateTime()
FryerLabel.config(text=FRYER\u ONE\u TIME\u VAR)
打破
打印(“反转前的TimerStayAlive:,TimerOnStayalive)#用于故障排除
timerOneStayAlive=反转(timerOneStayAlive)#将值反转回FALSE,以便理想情况下循环中断
#线程完成后,可以再次调用它
打印(“反转后的TimerStayAlive:,TimerOnStayalive)#用于故障排除
打印(“结束开始计数”)#用于故障排除
def计算时间(自我):
#打印(“已调用CalculateTime”)
lessThanTen=0
self.seconds=self.seconds-1
self.m,self.s=divmod(self.seconds,60)

如果self.spython社区有一个被广泛接受的样式指南,那将是非常幸运的。由于您的非pep8命名和格式,我很难快速理解您所说的问题

其他线程无法与小部件交互,只有tkinter mainloop可以这样做。您可以使用tkinter提供的after方法在mainloop中的给定时间段后运行函数

看这个例子 举个例子

明确地说,使用tkinter进行线程处理可能是可行的,但它是不可靠的,并且您将获得难以调试的意外行为。不要这样做

建议

  • 将调用tkinter after方法来启动一个函数,该函数无限期地检查队列中是否有要运行的函数。然后,它使用after方法安排tkinter运行这些函数

  • 我会有一个计时器类,包含start、stop和pause方法。 启动时,它将向上/向下计数,并在完成后重新启动。您创建的每个计时器实例还需要对tkinter按钮的引用

  • 要更新按钮,请在队列上放置一个函数

    tkinter_queue.put(lambda: but.config(text=i))
    
  • 现在,检查队列的函数将