【问题标题】:Running a function in the background for a GUI在后台为 GUI 运行函数
【发布时间】:2019-08-30 00:13:04
【问题描述】:

我遇到了功能/GUI 问题。我正在编写一个启动例程并检查时间是否在上午 8 点和某个停止时间之间的灯光功能。该例程从上午 8 点开始,并在该任意时间结束。问题是,一旦我在这个例程上点击开始,GUI 不会让我用那个开始按钮离开窗口,因为它卡在计时例程中。我真的很希望能够将计时器设置为在后台运行,然后能够离开该 GUI 的窗口。看起来线程是做到这一点的关键,但我不确定。

我将 GUI 代码放在一个单独的文件中,以简化我的工作。我还没有探索过任何 GUI 中断工具。

这是我的代码

import datetime
from threading import Timer
import tkinter as tk
import time

# =============================================================================
# userInput takes a formatted input and passes it back to main.
# =============================================================================
def userInput():
        try:
            startTime = datetime.datetime.strptime(input('When would you like the routine to start in HH:MM 24 hour format: '), "%H:%M").time()
            print (startTime.strftime("%H:%M"))
        except:
            print ("Please enter correct time in HHMM format")

        try:
            redTime = datetime.datetime.strptime(input('When would you to start the red shift in HH:MM 24 hour format: '), "%H:%M").time()
            print (redTime.strftime("%H:%M"))
        except:
            print ("Please enter correct time in HHMM format")

        return startTime, redTime

# =============================================================================
# timeComparator is a function which, if the user changes any settings or chooses
# start in the middle of a cycle, implements the correct routine depending on 
# where in the cycle it's in. Right now, it's being utitilized to just test.
# The actual function of this will be adjusted later.
# =============================================================================            
def timeComparator(startTimered, redTime):
    now = datetime.datetime.now().time()
    #this obtains the current time
    #if statement compares input from 
    print("The current time is: ", now)
    if (now < startTime):
        print ("hello human")
    elif (now > startTime):
        print ("hello plant")

# =============================================================================
# This routine is intended to be controlled by the user in the manual portion
# of the GUI. This receives the start time and returns secs to initiate the 
# timer.
# =============================================================================
def manualRoutine(startTime, redTime):

    now = datetime.datetime.now().time()
    nowSeconds = (((now.hour * 60) + now.minute) * 60) + now.second

    startSeconds = ((startTime.hour * 60) + startTime.minute) * 60
    secsStart = startSeconds - nowSeconds

    redSeconds = ((redTime.hour * 60) + redTime.minute) * 60
    nowSeconds = (((now.hour * 60) + now.minute) * 60) + now.second
    secsRed = redSeconds - nowSeconds

    return secsStart, secsRed

# =============================================================================
# This function references 8am and 8pm and checks to see if the current time is
# between these two time anchors. If it is, it'll implement the lights in the
# while loop. This is meant to be implemented for both the plant life and
# the automatic human routine.
# =============================================================================
def autoRoutine():
    now = datetime.datetime.now().time()
    autoStart = now.replace(hour=8, minute=0)

    stoptime = datetime.datetime.now().time()
    autoStop = stoptime.replace(hour=12, minute=29)
    #WS2812 code here that  the autoStart and autoStop

    if (now > autoStart and now < autoStop):
            keepprint = False 
#    
    while (keepprint == False):

        nowloop = datetime.datetime.now().time()
        #nowloop keeps track of the current time through each loop

        print("the lights are starting")
        time.sleep (1.0)
        if (nowloop >= autoStop):
            keepprint = True
        #this breaks the loop after the stop time

    print(autoStart.strftime("%H:%M"))

    return autoStart
# =============================================================================
# blueFade is the function call for the beginning of the day light start up and
# midday light continuity. This function will end at the end of the cycle and 
# will immediately be followed by the orangeFade() function. Also, this will 
# receive the redTime to determine when to stop the function right before the 
# red shift
# =============================================================================
def blueFade(redTime):    

    print("sanity")
    keepprint = False

    while (keepprint == False):
            nowloop = datetime.datetime.now().time()

            print("manual routine lights are on")
            time.sleep(1.0)
            if (nowloop >= redTime):
                keepprint = True

    print("the manual routine has stopped")

    #WS2812 code here
    #redTime will be used to end this code before redFade begins




# =============================================================================
# redFade is a function in where the fade from blue to a more reddish hue starts.
# Depending on when the user stated they wanted the red shift to start, this will
# begin at that time and then fade from blue, to a reddish hue, then to completely
# off. This will take exactly 30 minutes
# =============================================================================
def redFade():

    print("the red hue is being imprinted")

# =============================================================================
# Main function. Will be used to call all other functions. The 
# =============================================================================
if __name__=="__main__":

#    autoRoutine()

    startTime, redTime = userInput()

    timeComparator(startTime, redTime)

    secsBlue, secsRed = manualRoutine(startTime, redTime)

    bluelights = Timer(secsBlue, lambda: blueFade(redTime))
    redlights = Timer(secsRed, redTime)

    bluelights.start()
    redlights.start()

在上面的代码中,我想在后台运行 autoRoutine() 以及蓝灯和红灯。现在,如果我知道如何在后面运行 autoRoutine(),我可能会修复它以同时执行蓝色和红灯计时器。提前致谢。

【问题讨论】:

  • 您问题中的代码似乎根本没有使用任何 GUI。也就是说,一般来说,GUI 框架通常是用户事件驱动的,Python 的 tkinter 模块也是如此。见Tkinter, executing functions over time。然而,大多数提供了定期检查在“后台”中执行的任意代码状态的方法,例如在单独的线程或进程中。
  • 是的,GUI 是在另一个代码中实现的。为了理智,他们分开了。在一天结束时,我只是想保存 startTime 和 redTime

标签: python multithreading time


【解决方案1】:

您可以通过实现threading.Thread() 在后台运行事情,该threading.Thread() 会传递所需的任何参数。大多数时候,我发现while True: 循环效果最好,因此线程将永远运行,但在某些情况下,您可能希望.join() 线程回到主执行流程。我通常使用while some_global_variable == 1 执行此操作,然后您可以从函数外部对其进行编辑,或者只是等待线程自行到达分配函数的末尾。主要流程如下所示:

import threading # standard library, no pip-install
import tkinter as tk
import time

start = time.time()

win = tk.Tk()
label = tk.Label()
label.grid()

def worker_function():
    while True:
        now = time.time()
        elapsed = now - start
        label.configure(text='{:5.2}'.format(elapsed))

t = threading.Thread(target=worker_function, args=[])
t.daemon = True
t.start() # our thread will run until the end of the assigned function (worker_function here)
# this will never end because there is a while True: within worker_function

win.mainloop()

此外,您可以在提供的 [] 中为您的线程提供参数(即使只有 1 个参数,它也需要一个列表)。所以如果我们的函数是:

def worker_function(a, b, c, d):

我们的线程将被创建为:

t = threading.Thread(target=worker_function, args=[a, b, c, d])

还要注意它是target=worker_function 而不是target=worker_function()

【讨论】:

  • 即使我退出实现函数和变量存储的窗口并转到不同的窗口,线程是否会修复存储变量的问题?
  • @igomez 这取决于您的实施。很可能是的,它会解决它,因为一旦将变量传递给您的线程,它就不会忘记它们,直到它到达其分配函数的末尾(从不在 while True 循环中)。但是,如果您的线程试图更改不再存在的窗口,您将遇到异常!
  • 一般来说——对于一个带有多个小部件的更真实的 GUI——这种方法可能起作用,因为tkinter 不支持多线程,因为只有一个线程(通常是主线程)可以更新 GUI。要使用多线程,必须使用其他一些线程间通信方式,例如 Queue 以及 tkinter 小部件 after() 方法来定期检查其内容(并可能在必要时更新 GUI)。跨度>
  • 从严格的线程编程角度来看,要让主线程在worker_function线程仍在运行时退出,您应该在调用t.start()之前设置t.daemon = True。跨度>
  • @martineau 关于成为守护进程的好处。我说这会解决他的问题,因为在我看来,他希望线程在窗口关闭时运行,我将其解释为 OP 不想从线程更新窗口。尽管只有主线程可以更新 GUI,但您是正确的。但是,据我了解,从主线程创建的每个线程都算作一个主线程,并且只有当涉及到一个新的 进程 时,Queue 才变得必要......或者至少是这样当我遇到这些问题时。
猜你喜欢
  • 2013-10-01
  • 2020-08-29
  • 2021-04-27
  • 1970-01-01
  • 2015-10-29
  • 1970-01-01
  • 1970-01-01
  • 2022-11-17
  • 1970-01-01
相关资源
最近更新 更多