2011-08-21 43 views
10

Tôi đang sử dụng hàng đợi để trao đổi thư giữa chuỗi nền và ứng dụng GUI Tk. Hiện tại, điều này được thực hiện bằng cách gọi một phương thức truy vấn mọi lúc rồi.Tkinter: Đợi mặt hàng trong hàng đợi

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) 

Vấn đề với cách tiếp cận này là nếu UPDATE_TIME quá lớn, ứng dụng sẽ xử lý các mục mới chậm nhất có thể. Nếu nó quá nhỏ, Tk dành phần lớn thời gian kiểm tra hàng đợi mặc dù nó có thể làm những thứ khác trong thời gian chờ đợi.

Có cách nào để tự động kích hoạt phương thức read_queue bất cứ khi nào một mục mới đến trong hàng đợi không? (Tôi chắc chắn có thể gọi một phương thức trên Tk khi thread nền lấp đầy hàng đợi nhưng tôi sợ rằng điều này mang lại cho tôi một số vấn đề đồng thời - đó là lý do tại sao tôi đang sử dụng hàng đợi sau khi tất cả.)

+2

Dường như bạn có thể sử dụng event_generate từ chuỗi nền để kích hoạt sự kiện ảo trong GUI của mình. Có thể điều này có thể được sử dụng như một loại thông báo về trạng thái hàng đợi của bạn. http://groups.google.com/group/comp.lang.python/browse_thread/thread/3476fd30bec12367/853bb6f6dd216960?lnk=gst&q=brunel+%2Bevent_generate#853bb6f6dd216960 –

+0

Dường như đang hoạt động. Cảm thấy tự do để thêm nó như là một câu trả lời thực sự. – Debilski

Trả lời

5

TÓM TẮT: Tôi sẽ không sử dụng "noob oddy's example code" - là một cách tiếp cận cơ bản không hoàn thiện.

Tôi không phải là guru trăn, nhưng mã ví dụ được cung cấp bởi "noob oddy" (gọi là root.event_generate (...) trong chuỗi nền) dường như là "phương pháp cơ bản không hoàn thiện". tức là, có một số bài viết trên internet có trạng thái "không bao giờ gọi các hàm Tkinter/các phương thức đối tượng bên ngoài ngữ cảnh của 'GUI thread'" (thường là luồng chính). Ví dụ của ông hoạt động "hầu hết thời gian", nhưng nếu bạn tăng tỷ lệ tạo sự kiện, thì "tỷ lệ lỗi" của ví dụ sẽ tăng - tuy nhiên, hành vi cụ thể phụ thuộc vào tốc độ tạo sự kiện và đặc điểm hiệu suất của nền tảng.

Ví dụ, sử dụng mã của mình với Python 2.7.3, nếu bạn thay đổi:

 time.sleep(1) 

tới:

 time.sleep(0.01) 

sau đó kịch bản/app thường sẽ sụp đổ sau khi 'x' số lặp lại. Sau khi tìm kiếm nhiều, nếu bạn "phải sử dụng Tkinter", thì nó xuất hiện "phương pháp chống đạn" nhất để nhận thông tin từ một chuỗi nền tới luồng GUI là sử dụng phương thức widget 'after()' để thăm dò ý kiến đối tượng an toàn chủ đề (chẳng hạn như 'Hàng đợi'). ví dụ:

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

Cảm ơn bạn đã làm rõ. Tôi quên rằng tôi đã hỏi câu hỏi này. Trong mã của tôi, cuối cùng tôi đã giải quyết hầu hết cùng một giải pháp bằng cách sử dụng 'after' (và thêm một số yếu tố GUI để tinh chỉnh thời gian chờ đợi - hiệu suất hơi khác tùy thuộc vào hệ điều hành) và không rối tung xung quanh với trình quản lý sự kiện nội bộ của Tk . – Debilski

+1

mã này có lỗi. Trong 'thread_proc' bạn làm' dataQ.put (v) 'mà không định nghĩa' v'. –

+1

Bạn nên xóa trong khi True, thay thế try/except/break bằng cách đơn giản nếu và thay thế get (timeout = 0.1) bằng cách mở khóa nhận được: Kết quả tương tự, mã rõ hơn (mạnh mẽ hơn). (Và ứng dụng của bạn sẽ không miễn phí 100ms mỗi 100ms chờ hàng đợi của bạn) –

13

Một lựa chọn có thể là mtTkinter http://tkinter.unpythonic.net/wiki/mtTkinter

Dưới đây là một ví dụ của việc sử dụng event_generate từ một sợi nền:

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

Ngoài ra còn có đề cập đến sử dụng after_idle theo cách tương tự.
tức là. root.after_idle (timeChanged)

+1

Sử dụng 'generate_event' hấp dẫn về mặt khái niệm hơn và hai ví dụ của @noob oddy đang hoạt động. Sử dụng chúng làm cơ sở, tôi đã nhúng một con số matplotlib để tạo ra một cốt truyện thời gian thực truy xuất dữ liệu qua mạng. Điều này làm việc đúng trong Linux nhưng không phải Windows (XP, 7,8,1 tất cả đều cư xử theo cách tương tự) .Vấn đề này có vẻ liên quan đến một loạt các cuộc gọi event_generate khi chương trình bắt đầu. Nó có thể bị né tránh bằng cách chờ tất cả dữ liệu đã tích lũy đến và sau đó tạo ra một sự kiện duy nhất. Nhưng các thông báo lỗi đã khiến tôi tin rằng 'event_generate' ** không phải là luồng an toàn trong cửa sổ ** – NameOfTheRose

+0

** mtTkinter ** giải quyết các vấn đề cửa sổ (được quản lý để tồn tại trong số 80.000 sự kiện). Điều này, một cách gián tiếp, xác nhận nó là một vấn đề luồng. ** mtTkinter **, đằng sau khung cảnh, sử dụng phương thức 'after', vì vậy tôi không thấy nhiều điểm trong việc sử dụng nó. – NameOfTheRose

+0

Tôi đã biết rằng Tkinter được biên soạn không có hỗ trợ luồng trong cài đặt Windows Python của tôi, trong khi cài đặt Linux được biên dịch với hỗ trợ luồng (ít nhất đó là những gì mtTkinter báo cáo bằng cách sử dụng 'root.globalgetvar ('tcl_platform (threaded) ') '). Đây có lẽ là lý do của sự khác biệt trong hành vi. – NameOfTheRose

2

Bỏ phiếu có thể được loại bỏ khỏi giải pháp Ken Mumme bằng cách sử dụng os.pipe để đồng bộ hóa giữa hai luồng.

tkinter có phương thức createFilehandler có thể được sử dụng để thêm bộ mô tả tệp vào vòng chọn của tk. Sau đó bạn có thể báo hiệu rằng một cái gì đó đã sẵn sàng trong hàng đợi bằng cách viết một byte vào đường ống.

Các giải pháp trông như thế này:

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

Tôi không phải là một chuyên gia về trăn; xin lỗi nếu tôi đã sai lầm về bất kỳ quy ước mã hóa nào. Tuy nhiên, tôi rất tuyệt với đường ống :)

+2

FYI không có hỗ trợ 'createfilehandler()' trên Windows, một Hàng đợi được thăm dò là điều tốt nhất bạn có thể làm. – shuckc

Các vấn đề liên quan