Tkinter:等待队列中的项目

发布于 2024-11-30 14:08:23 字数 482 浏览 1 评论 0原文

我正在使用队列在后台线程和 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)

这种方法的问题是,如果 UPDATE_TIME 太大,应用程序处理新项目的速度将比可能的慢。如果它太小,Tk 会花费大部分时间检查队列,尽管它同时可以做其他事情。

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

I’m using a queue to exchange messages between a background thread and a Tk GUI application. Currently, this is done by calling a query method every now and then.

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)

The problem with this approach is that if UPDATE_TIME is too large, the application will process new items slower than possible. If it is too small, Tk spends most of the time checking the queue although it could do other stuff in the meantime.

Is there a way to automatically trigger the read_queue method whenever a new item arrives in the queue? (I could certainly call a method on Tk when the background thread fills the queue but I fear that this gives me some concurrency issues – that’s why I’m using queues after all.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

疯狂的代价 2024-12-07 14:08:23

一种选择可能是 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()

还提到以类似的方式使用 after_idle 。
即。 root.after_idle(时间更改)

One option might be mtTkinter http://tkinter.unpythonic.net/wiki/mtTkinter

Here is another example of using event_generate from a background thread:

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

There is also mention of using after_idle in a similar way.
ie. root.after_idle(timeChanged)

浅沫记忆 2024-12-07 14:08:23

摘要:我不会使用“noob oddy's example code”——这是一种根本上有缺陷的方法。

我不是 python 专家,但“noob oddy”提供的示例代码(调用 root.event_generate(. ..)在后台线程内)似乎是一种“根本上有缺陷的方法”。即,互联网上有几篇文章指出“永远不要在‘GUI 线程’(通常是主线程)的上下文之外调用 Tkinter 函数/对象方法”。
他的示例“大部分时间”都有效,但如果增加事件生成率,则该示例的“崩溃率”将会增加——但是,具体行为取决于事件生成率和平台的性能特征。

例如,在 Python 2.7.3 中使用他的代码,如果将:更改

       time.sleep(1)

为:,

       time.sleep(0.01)

则脚本/应用程序通常会在“x”次迭代后崩溃。

经过大量搜索,如果您“必须使用 Tkinter”,那么从后台线程获取信息到 GUI 线程的最“防弹方法”似乎是使用 'after()' 小部件方法来轮询线程安全对象(例如“队列”)。例如,

################################################################################
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 #################################################################

SUMMARY: I wouldn't use "noob oddy's example code" -- is a fundamentally flawed approach.

I'm not a python guru, but the example code provided by "noob oddy" (which calls root.event_generate(...) within the background thread) appears to be a "fundamentally flawed approach". i.e., there are several articles on the internet which state "to never invoke Tkinter functions/object methods outside the context of the 'GUI thread'" (which is typically the main thread).
His example works "most of the time", but if you increase the event generation rate, then the example's "crash rate" will increase -- however, specific behavior is dependent on the event generation rate and the platform's performance characteristics.

For example, using his code with Python 2.7.3, if you change:

       time.sleep(1)

to:

       time.sleep(0.01)

then the script/app will typically crash after 'x' number of iterations.

After much searching, if you "must use Tkinter", then it appears the most "bullet proof method" of getting information from a background thread to the GUI thread is to use 'after()' widget method to poll a thread-safe object (such as 'Queue'). e.g.,

################################################################################
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 #################################################################
給妳壹絲溫柔 2024-12-07 14:08:23

通过使用 os.pipe 在两个线程之间进行同步,可以从 Ken Mumme 解决方案中消除轮询。

tkinter 有一个 createFilehandler 方法,可用于将文件描述符添加到 tk 的选择循环中。然后,您可以通过向管道中写入一个字节来表示队列中的某些内容已准备就绪。

解决方案如下:

import Queue
import os

uiThreadQueue = Queue.Queue() ;

pipe_read, pipe_write = os.pipe() ;

# call one function from the queue.  Triggered by the 
# pipe becoming readable through root.tk.createfilehandler().
def serviceQueue(file, mask):
    os.read(pipe_read, 1) 
    func = uiThreadQueue.get() ;
    func() 

# enqueue a function to be run in the tkinter UI thread.
# best used as inUIThread(lambda: self.callSomeFunction(arg1,arg2,arg3))
def inUIThread(f):
    uiThreadQueue.put(f)
    os.write(pipe_write, "x")

... set up your widgets, start your threads, etc.....


root.tk.createfilehandler(pipe_read, tkinter.READABLE, serviceQueue)
root.mainloop()

我不是Python专家;如果我搞砸了任何编码约定,我深表歉意。不过我对管道很在行:)

Polling can be eliminated from the Ken Mumme solution by using os.pipe to synchronise between the two threads.

tkinter has a createFilehandler method that can be used to add a file descriptor into tk's select loop. You can then signal that something is ready in the queue by writing a byte into the pipe.

The solution looks like this:

import Queue
import os

uiThreadQueue = Queue.Queue() ;

pipe_read, pipe_write = os.pipe() ;

# call one function from the queue.  Triggered by the 
# pipe becoming readable through root.tk.createfilehandler().
def serviceQueue(file, mask):
    os.read(pipe_read, 1) 
    func = uiThreadQueue.get() ;
    func() 

# enqueue a function to be run in the tkinter UI thread.
# best used as inUIThread(lambda: self.callSomeFunction(arg1,arg2,arg3))
def inUIThread(f):
    uiThreadQueue.put(f)
    os.write(pipe_write, "x")

... set up your widgets, start your threads, etc.....


root.tk.createfilehandler(pipe_read, tkinter.READABLE, serviceQueue)
root.mainloop()

I'm not a python expert; apologies if I've messed up on any coding conventions. I'm great with pipes, though :)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文