【问题标题】:While Loop Locks ApplicationWhile 循环锁定应用程序
【发布时间】:2015-02-20 22:19:36
【问题描述】:

我已经在我正在开发的应用程序上苦苦挣扎了一段时间。经过数小时尝试调试界面锁定且无法发生其他任何事情的问题后,我发现这是可怕的 While 循环。请参阅下面的示例并运行它。当您通过单击按钮启动 while 循环时,您无法在屏幕上执行任何其他操作。在这种情况下,它只是一个需要按下的简单警报按钮。

from Tkinter import *
import tkMessageBox

root = Tk()
root.geometry("450x250+300+300")
root.title("Raspberry PI Test")

def myloop():
    count = 0
    while (count < 500):
       print 'The count is:', count
       count = count + 1

    print "Good bye!"

def mymessage():
    tkMessageBox.showinfo(title="Alert", message="Hello World!")

buttonLoop = Button(root, text="Start Loop", command=myloop)
buttonLoop.place(x=5, y=15)

buttonMessage = Button(root, text="Start Loop", command=mymessage)
buttonMessage.place(x=85, y=15)


root.mainloop()

如何让一个循环在计数完成之前一直运行,并且仍然能够在我的应用程序中执行其他任务?我还应该注意,我已经使用 Thread 尝试过同样的事情,但这并不重要。 UI 仍在等待 While 循环结束,然后您才能执行任何操作。

【问题讨论】:

  • 如果您使用线程则不会...所以我不确定您尝试使用线程是什么意思
  • 好吧,如果您 等待 让您的线程返回,那么是的,这会像内联那样阻塞。不幸的是,我对 TK 不熟悉(我怀疑它的存货不是很漂亮);也许您可以在设置 TK event loop 时使用一些帮助,这应该可以让您在不使用子进程/线程的情况下实时处理和处理 UI 事件。我认为线程模块可能会对多核主机的性能造成重大影响,但这可能仅适用于 c 扩展。

标签: python tkinter


【解决方案1】:

既然我更了解你想要什么(秒表),我会推荐 root.after 命令

from Tkinter import *
import tkMessageBox
import threading
import time
root = Tk()
root.geometry("450x250+300+300")
root.title("Raspberry PI Test")
print dir(root)
count = 0
def start_counter():
    global count
    count = 500
    root.after(1,update_counter)
def update_counter():
    global count
    count -= 1
    if count < 0:
        count_complete()
    else:
        root.after(1,update_counter)

def count_complete():
    print "DONE COUNTING!! ... I am now back in the main thread"
def mymessage():
    tkMessageBox.showinfo(title="Alert", message="Hello World!")

buttonLoop = Button(root, text="Start Loop", command=myloop)
buttonLoop.place(x=5, y=15)

buttonMessage = Button(root, text="Start Loop", command=mymessage)
buttonMessage.place(x=85, y=15)


root.mainloop()

(原答案如下)

使用线程

from Tkinter import *
import tkMessageBox
import threading
import time
root = Tk()
root.geometry("450x250+300+300")
root.title("Raspberry PI Test")
print dir(root)
def myloop():
    def run():
        count = 0
        while (count < 500) and root.wm_state():
           print 'The count is:', count
           count = count + 1
           time.sleep(1)

        root.after(1,count_complete)
    thread = threading.Thread(target=run)
    thread.start()
def count_complete():
    print "DONE COUNTING!! ... I am now back in the main thread"
def mymessage():
    tkMessageBox.showinfo(title="Alert", message="Hello World!")

buttonLoop = Button(root, text="Start Loop", command=myloop)
buttonLoop.place(x=5, y=15)

buttonMessage = Button(root, text="Start Loop", command=mymessage)
buttonMessage.place(x=85, y=15)


root.mainloop()

请注意,当您显示将在 Windows api 级别阻塞的信息框时,线程计数将等到关闭...以解决这个问题,我认为您可以用多处理替换线程

【讨论】:

  • 嗯,也许我在尝试这个时做错了线程。我要试一试,看看它是否有帮助。此外,消息框只是测试这一点的一种方式,而不是阅读问题的一部分。我会在尝试后发表评论。谢谢!
  • 非常感谢乔兰。这正是我所需要的。我想我试图让线程变得更复杂,我需要这样做。你的例子正是我所需要的。我将发布另一个示例,以便其他人可以看到它。现在这会将计数显示到一个标签中,以表明它确实更新了 UI。
【解决方案2】:

我对 TKinter 了解不多,但从我的阅读中可以清楚地看出,您需要在 while 循环中使用一些 TKinter 方法来更新您的文本框。 TKinter 在事件循环上运行,因此您必须从代码中发送信号以重新进入 TKinter 的执行。

您发现您的 while 循环阻止执行您的 UI 更新,您做得很好。因此,您需要暂停counting's 执行并让TKinter 更新UI,而不是线程。

tutorial 提供了一个很好的例子。关键是在第 24 行,他打电话给root.update,我相信这会中断你的程序,让 TKinter 做这件事。

【讨论】:

  • 我会查看教程,但我认为这对我需要的东西不起作用。这是我的应用程序中发生的事情的一个很好的例子。秒表启动并在运行 mm:ss:hs 时显示在屏幕上。然后应用程序启动读取数据的 While 语句。现在发生的事情是秒表在 while 语句执行时停止显示任何内容。我不敢相信,让 While 暂停足够长的时间来更新屏幕会在没有秒表视觉跳跃的情况下工作。希望有更好的办法。
  • 有一百万种更好的方法
【解决方案3】:

这是最后的代码,只是为了证明线程可以工作。计数在发生的同时显示在屏幕上。再次感谢乔兰!

from Tkinter import *
import tkMessageBox
import threading
import time
root = Tk()
root.geometry("450x250+300+300")
root.title("Raspberry PI Test")
showResults = StringVar()
showResults.set('0')
print dir(root)
def myloop():
    def run():
        count = 0
        while (count < 1000) and root.wm_state():
           print 'The count is:', count
           showResults.set(count)
           count = count + 1
           #time.sleep(1)

        root.after(1,count_complete)
    thread = threading.Thread(target=run)
    thread.start()
def count_complete():
    print "DONE COUNTING!! ... I am now back in the main thread"
def mymessage():
    tkMessageBox.showinfo(title="Alert", message="Hello World!")

buttonLoop = Button(root, text="Start Loop", command=myloop)
buttonLoop.place(x=5, y=15)

buttonMessage = Button(root, text="Message", command=mymessage)
buttonMessage.place(x=85, y=15)

l2 = Label(root, width=15, height=4, font=("Helvetica", 16), textvariable=showResults, background="black", fg="green")
l2.place(x=15, y=65)

root.mainloop()

【讨论】: