2011-08-21 5 views
10

대기열을 사용하여 백그라운드 스레드와 Tk GUI 응용 프로그램간에 메시지를 교환하고 있습니다. 현재이 작업은 매번 쿼리 메서드를 호출하여 수행됩니다.Tkinter : 대기열에있는 항목 대기

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에의 메소드를 호출 할 수 있지만이 좀 동시성 문제를 제공 두려움 - 그건 내가 결국 큐를 사용하고 있습니다 이유.)

+2

분명히 GUI에서 가상 이벤트를 발생시키는 백그라운드 스레드의 event_generate를 사용할 수 있습니다. 대기열 상태에 대한 일종의 알림으로 사용될 수 있습니다. http://groups.google.com/group/comp.lang.python/browse_thread/thread/3476fd30bec12367/853bb6f6dd216960?lnk=gst&q=brunel+%2Bevent_generate#853bb6f6dd216960 –

+0

작동하고있는 것으로 보입니다. 진정한 답변으로 추가하십시오. – Debilski

답변

5

요약 : "noob oddy's example code"은 근본적으로 결함이있는 접근 방식입니다.

저는 파이썬 전문가는 아니지만 "noob oddy"(백그라운드 스레드 내에서 root.event_generate (...)를 호출하는) 예제 코드는 "근본적으로 결함이있는 접근"인 것처럼 보입니다. 즉, "GUI 스레드"(일반적으로 주 스레드)의 컨텍스트 외부에서 Tkinter 함수/개체 메서드를 호출하지 말라는 인터넷상의 여러 기사가 있습니다. 그의 예는 "대부분의 경우"작동하지만 이벤트 생성 속도를 높이면 예제의 "충돌 속도"가 증가하지만 특정 동작은 이벤트 생성 속도와 플랫폼의 성능 특성에 따라 다릅니다.

 time.sleep(1) 

에 :

 time.sleep(0.01) 

는 스크립트/응용 프로그램은 보통의 'x'를 지나면 충돌합니다 변경하면, 파이썬 2.7.3와 함께 자신의 코드를 사용하여 예를 들어

, 반복.

"Tkinter를 사용해야 만하는 경우"백그라운드 스레드에서 GUI 스레드로 정보를 가져 오는 가장 "불완전한 방법"이 'after()'위젯 메소드를 사용하여 스레드 안전 객체 (예 : 'Queue'). 예

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

설명해 주셔서 감사합니다. 나는이 질문을 한 것을 잊었다. 필자의 코드에서는 결국'after'를 사용하여 거의 동일한 솔루션을 사용했고 (대기 시간을 미세 조정하기 위해 일부 GUI 요소를 추가했습니다. 성능은 OS에 따라 약간 씩 다름) Tk의 내부 이벤트 관리자 자체를 사용하지 않았습니다. . – Debilski

+1

이 코드에는 버그가 있습니다. 'thread_proc'에서'v'를 먼저 정의하지 않고'dataQ.put (v)'를합니다. –

+1

while을 제거하고 try/except/break를 simple if로 대체하고 get (timeout = 0.1)을 unblocking get으로 대체하십시오. 동일한 결과, 더 명확하고 (더 견고한) 코드. (그리고 당신의 앱은 대기열을 기다리는 100ms마다 100ms를 자유롭게하지 않을 것입니다.) –

13

하나의 옵션은 http://tkinter.unpythonic.net/wiki/mtTkinter

mtTkinter 수 있습니다

##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 사용하는 언급도있다 :

여기 백그라운드 스레드에서 event_generate를 사용하는 또 다른 예이다.
즉. root.after_idle (timeChanged)

+1

'generate_event'를 사용하는 것이 개념적으로 더 매력적이며, @noob oddy의 두 예제가 효과적입니다. 기초로 사용하여 네트워크를 통해 데이터를 검색하는 실시간 플롯을 만들기 위해 matplotlib 그림을 삽입했습니다. 이것은 리눅스에서는 제대로 작동하지만 Windows에서는 그렇지 않습니다 (XP, 7,8.1은 모두 비슷한 방식으로 동작합니다.) 문제는 프로그램이 시작될 때 event_generate 호출과 관련이있는 것으로 보입니다. 이미 축적 된 모든 데이터가 도착하기를 기다리면 회피 될 수 있습니다 단일 이벤트를 생성합니다. 그러나 오류 메시지로 인해'event_generate' **가 윈도우에서 스레드 세이프가 아닌 것으로 믿을 수있었습니다 ** – NameOfTheRose

+0

** mtTkinter **는 80.000 이벤트의 버스트에서 생존하기 위해 관리되는 Windows 문제를 해결합니다. 이것은 간접적 인 방식으로 스레드 문제임을 확인합니다. ** mtTkinter **는 장면 뒤에서'after' 메서드를 사용하므로 사용법에 대해서는별로 알지 못합니다. – NameOfTheRose

+0

나는 Tkinter가 Windows Python 설치에서 스레드 지원없이 컴파일된다는 것을 알게되었고, Linux 설치가 스레드 지원으로 컴파일되었지만 (적어도 mtTkinter가'root.globalgetvar ('tcl_platform (threaded) ')'). 이것은 아마 행동의 차이의 이유입니다. – NameOfTheRose

2

폴링 개의 스레드간에 동기화 os.pipe를 사용하여 켄 Mumme 용액으로부터 제거 될 수있다.

tkinter에는 tk의 select 루프에 파일 설명자를 추가하는 데 사용할 수있는 createFilehandler 메소드가 있습니다. 그런 다음 파이프에 바이트를 쓰면 대기열에 준비가되었음을 알릴 수 있습니다.

이 솔루션은 다음과 같습니다

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

내가 파이썬 전문가가 아니에요; 내가 코딩 규칙을 망쳤다면 사과드립니다. 그래도 파이프와 잘 맞습니다 :)

+2

참고 Windows에서는'createfilehandler()'지원이 없습니다. 폴링 된 대기열은 할 수있는 최선의 방법입니다. – shuckc