Tkinter: ожидание элемента в очереди - PullRequest
10 голосов
/ 22 августа 2011

Я использую очередь для обмена сообщениями между фоновым потоком и приложением 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, когда фоновый поток заполняет очередь, но я боюсь, что это вызывает некоторые проблемы с параллелизмом - вот почему я все-таки использую очереди.)

Ответы [ 3 ]

15 голосов
/ 26 августа 2011

Один из вариантов может быть 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 (timeChanged)

5 голосов
/ 11 февраля 2013

РЕЗЮМЕ: Я бы не использовал « пример кода noob oddy » - это принципиально ошибочный подход.

Я не гуру Python, но пример кода, предоставленный« noob oddy » (который вызывает root.event_generate (...) в фоновом потоке) представляется «принципиально ошибочным подходом».то есть в Интернете есть несколько статей, в которых говорится, что «никогда не вызывать функции / методы объекта Tkinter вне контекста потока GUI» (который обычно является основным потоком).Его пример работает «большую часть времени», но если вы увеличите частоту генерации событий, то «частота отказов» примера увеличится, однако конкретное поведение зависит от частоты генерации событий и характеристик производительности платформы.

Например, используя его код с 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 #################################################################
3 голосов
/ 26 ноября 2013

Опрос можно исключить из решения Ken Mumme с помощью os.pipe для синхронизации между двумя потоками.

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

Я не эксперт по питонам; извиняюсь, если я напутал на любых соглашениях о кодировании. Я отлично с трубами, хотя:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...