Tkinter:等待队列中的项目

Deb*_*ski 11 python multithreading tkinter event-handling

我正在使用队列在后台线程和Tk GUI应用程序之间交换消息.目前,这是通过不时调用查询方法来完成的.

def read_queue(self):
    try:
        self.process(self.queue.get(False)) # non-blocking
    except Queue.Empty:
        pass
    finally:
        self.after(UPDATE_TIME, self.read_queue)
Run Code Online (Sandbox Code Playgroud)

这种方法的问题是,如果UPDATE_TIME太大,应用程序将比可能的更慢地处理新项目.如果它太小,Tk花费大部分时间来检查队列,虽然它可以在此期间做其他事情.

有没有办法read_queue在新项目到达队列时自动触发方法?(当后台线程填满队列时,我当然可以在Tk上调用一个方法,但我担心这会给我带来一些并发问题 - 这就是我毕竟使用队列的原因.)

小智 15

一个选项可能是mtTkinter http://tkinter.unpythonic.net/wiki/mtTkinter

以下是从后台线程使用event_generate的另一个示例:

##The only secure way I found to make Tkinter mix with threads is to never  
##issue commands altering the graphical state of the application in another  
##thread than the one where the mainloop was started. Not doing that often  
##leads to random behaviour such as the one you have here. Fortunately, one  
##of the commands that seems to work in secondary threads is event_generate,  
##giving you a means to communicate between threads. If you have to pass  
##information from one thread to another, you can use a Queue.
##
##This obviously complicates things a bit, but it may work far better.  
##Please note that the 'when' option *must* be specified in the call to  
##event_generate and *must not* be 'now'. If it's not specified or if it's  
##'now', Tkinter may directly execute the binding in the secondary thread's  
##context. (Eric Brunel)

import threading
import time
import Queue
from Tkinter import *

## Create main window
root = Tk()

## Communication queue
commQueue = Queue.Queue()

## Function run in thread
def timeThread():
    curTime = 0
    while 1:
        ## Each time the time increases, put the new value in the queue...
        commQueue.put(curTime)
        ## ... and generate a custom event on the main window
        try:
            root.event_generate('<<TimeChanged>>', when='tail')
        ## If it failed, the window has been destoyed: over
        except TclError:
            break
        ## Next
        time.sleep(1)
        curTime += 1

## In the main thread, do usual stuff
timeVar = IntVar()
Label(root, textvariable=timeVar, width=8).pack()

## Use a binding on the custom event to get the new time value
## and change the variable to update the display
def timeChanged(event):
    timeVar.set(commQueue.get())

root.bind('<<TimeChanged>>', timeChanged)

## Run the thread and the GUI main loop
th=threading.Thread(target=timeThread)
th.start()

root.mainloop()
Run Code Online (Sandbox Code Playgroud)

还提到以类似的方式使用after_idle.
即.root.after_idle(timeChanged)

  • 使用`generate_event` 在概念上更具吸引力,@nooboddy 的两个例子正在发挥作用。使用它们作为基础,我嵌入了一个 matplotlib 图,以便创建一个通过网络检索数据的实时绘图。这在 Linux 中运行正常,但在 Windows 中不能正常工作(XP、7、8.1 都以类似的方式运行)。这个问题似乎与程序启动时爆发的 event_generate 调用有关。可以通过等待所有已经积累的数据到达来避免然后生成一个事件。但是错误消息让我相信`event_generate` **在windows中不是线程安全的** (2认同)

小智 5

总结:我不会使用" noob oddy的示例代码 " - 这是一个根本上有缺陷的方法.

我不是python guru,但是" noob oddy "(在后台线程中调用root.event_generate(...))提供的示例代码似乎是一种"根本上有缺陷的方法".也就是说,互联网上有几篇文章声明"永远不要在'GUI线程'的上下文之外调用Tkinter函数/对象方法"(通常是主线程).他的示例"大部分时间都在工作",但如果您提高事件生成率,那么示例的"崩溃率"将会增加 - 但是,具体行为取决于事件生成率和平台的性能特征.

例如,使用他的代码与Python 2.7.3,如果你改变:

       time.sleep(1)
Run Code Online (Sandbox Code Playgroud)

至:

       time.sleep(0.01)
Run Code Online (Sandbox Code Playgroud)

然后脚本/应用程序通常会在'x'次迭代后崩溃.

经过多次搜索,如果你"必须使​​用Tkinter",那么从后台线程获取信息到GUI线程的最"防弹方法"就是使用'after()'widget方法来轮询一个线程安全的对象(例如'队列').例如,

################################################################################
import threading
import time
import Queue
import Tkinter      as Tk
import Tkconstants  as TkConst
from ScrolledText import ScrolledText
from tkFont       import Font

global top
global dataQ
global scrText

def thread_proc():
    x = -1
    dataQ.put(x)
    x = 0
    for i in xrange(5):
        for j in xrange(20):
            dataQ.put(x)
            time.sleep(0.1)
            x += 1
        time.sleep(0.5)
    dataQ.put(x)

def on_after_elapsed():
    while True:
        try:
            v = dataQ.get(timeout=0.1)
        except:
            break
        scrText.insert(TkConst.END, "value=%d\n" % v)
        scrText.see(TkConst.END)
        scrText.update()
    top.after(100, on_after_elapsed)

top     = Tk.Tk()
dataQ   = Queue.Queue(maxsize=0)
f       = Font(family='Courier New', size=12)
scrText = ScrolledText(master=top, height=20, width=120, font=f)
scrText.pack(fill=TkConst.BOTH, side=TkConst.LEFT, padx=15, pady=15, expand=True)
th = threading.Thread(target=thread_proc)
th.start()
top.after(100, on_after_elapsed)
top.mainloop()
th.join()
## end of file #################################################################
Run Code Online (Sandbox Code Playgroud)

  • -1,noob oddy方式不是"根本上有缺陷",带有`(when ='tail')`的`event_generate`可以从另一个线程调用.请参阅*从Tkinter讨论邮件列表中的线程*主题唤醒Tk事件循环:*带有虚拟事件的event generate()就像魅力一样*说Guido.它避免了轮询(更具反应性的GUI,更少的功耗),并且已知`after`配方在MacOsX上不稳定(仅对其他GUI事件做出反应:窗口焦点,鼠标移动......).我会删除我的downvote如果你改写你的答案为"共同的配方......"而不是"别人是错的,我是对的". (8认同)