2012-08-05 65 views
3

我正在運行一個Tkinter GUI,它使用subprocess.Popen(...)關閉另一個進程(python腳本),並使用管道作爲stdout和stderr。然後我旋轉一個單獨的線程,以異步讀取該過程中的out/err,並使用threading.Thread將其繪製到Tkinter Text小部件中。Tkinter GUI只在鼠標移動時更新

除了異步,一切都很好。只有當我移動鼠標或按下鍵盤上的按鍵時纔會執行讀取線程。我甚至將打印語句放入線程函數中,當我將鼠標移動到圓圈中時,它們開始/停止打印。

這裏的異步讀取的類,我使用,從here借:

class AsynchronousFileReader(threading.Thread): 
    ''' 
    Helper class to implement asynchronous reading of a file 
    in a separate thread. Pushes read lines on a queue to 
    be consumed in another thread. 
    ''' 

    def __init__(self, fd, queue): 
     assert isinstance(queue, Queue.Queue) 
     assert callable(fd.readline) 
     threading.Thread.__init__(self) 
     self._fd = fd 
     self._queue = queue 

    def run(self): 
     '''The body of the tread: read lines and put them on the queue.''' 
     for line in iter(self._fd.readline, ''): 
      self._queue.put(line) 

    def eof(self): 
     '''Check whether there is no more content to expect.''' 
     return not self.is_alive() and self._queue.empty() 

我的消費方式拉的消息了異步文件閱讀器(這是一個單獨的線程運行在一個:

def consume(self, process, console_frame): 
    # Launch the asynchronous readers of the process' stdout and stderr. 

    stdout_queue = Queue.Queue() 
    stdout_reader = AsynchronousFileReader(process.stdout, stdout_queue) 
    stdout_reader.start() 
    stderr_queue = Queue.Queue() 
    stderr_reader = AsynchronousFileReader(process.stderr, stderr_queue) 
    stderr_reader.start() 

    # Check the queues if we received some output (until there is nothing more to get). 
    while not stdout_reader.eof() or not stderr_reader.eof(): 
     # Show what we received from standard output. 
     while not stdout_queue.empty(): 
      line = stdout_queue.get() 
      console_frame.writeToLog(line.strip(), max_lines=None) 
      time.sleep(.03) # prevents it from printing out in large blocks at a time 

     # Show what we received from standard error. 
     while not stderr_queue.empty(): 
      line = stderr_queue.get() 
      console_frame.writeToLog(line.strip(), max_lines=None) 
      time.sleep(.03) # prevents it from printing out in large blocks at a time 

     # Sleep a bit before asking the readers again. 
     time.sleep(.05) 

    # Let's be tidy and join the threads we've started. 
    stdout_reader.join() 
    stderr_reader.join() 

    # Close subprocess' file descriptors. 
    process.stdout.close() 
    process.stderr.close() 

    print "finished executing" 
    if self.stop_callback: 
     self.stop_callback() 

就像我之前說的 - 當我移動鍵盤上的鼠標類型consume()線程只執行 - 這意味着writeToLog(...)功能(文本追加到Tkinter的GUI)僅被執行w ^母雞鼠標/鍵盤活動發生......任何想法?

編輯:我想我有可能發生的事情的想法......如果我評論的writeToLog(...)呼叫,並用一個簡單的印刷(取Tkinter的出方程)替換它,則消耗線程正常執行。看來Tkinter是這裏的問題。我的任何想法都可以從消費線程完成Tkinter文本小部件更新?

編輯2:得到它的工作感謝意見。這裏是我使用的最終代碼:

gui_text_queue = Queue.Queue() 


def consume(self, process, console_frame): 
    # Launch the asynchronous readers of the process' stdout and stderr. 

    stdout_queue = Queue.Queue() 
    stdout_reader = AsynchronousFileReader(process.stdout, stdout_queue) 
    stdout_reader.start() 
    stderr_queue = Queue.Queue() 
    stderr_reader = AsynchronousFileReader(process.stderr, stderr_queue) 
    stderr_reader.start() 

    # Check the queues if we received some output (until there is nothing more to get). 
    while not stdout_reader.eof() or not stderr_reader.eof(): 
     # Show what we received from standard output. 
     while not stdout_queue.empty(): 
      line = stdout_queue.get() 
      gui_text_queue.put(line.strip()) 

     # Show what we received from standard error. 
     while not stderr_queue.empty(): 
      line = stderr_queue.get() 
      gui_text_queue.put(line.strip()) 

     # Sleep a bit before asking the readers again. 
     time.sleep(.01) 

    # Let's be tidy and join the threads we've started. 
    stdout_reader.join() 
    stderr_reader.join() 

    # Close subprocess' file descriptors. 
    process.stdout.close() 
    process.stderr.close() 

    if self.stop_callback: 
     self.stop_callback() 

添加了這個方法我Tkinter的控制檯框架,並在框架初始化函數的末尾把它稱爲一次:

def pull_text_and_update_gui(self): 
    while not gui_text_queue.empty(): 
     text = gui_text_queue.get() 
     self.writeToLog(text, max_lines=None) 
    self.after(5, self.pull_text_and_update_gui) 
+0

你是怎麼調用消耗方法的?你把它稱爲一個線程。它在另一個線程中啓動? – jdi 2012-08-05 23:10:38

+0

t =線程(target = self.consume,args = [proc,console_frame]); t.start() – skandocious 2012-08-05 23:14:49

+0

我必須在另一個線程中啓動consume(),否則它會掛起GUI。 – skandocious 2012-08-05 23:16:25

回答

3

的Tkinter不是線程安全的。如果您的writeToLog函數嘗試將數據插入到文本小部件中,您將獲得不可預知的行爲。爲了讓單獨的線程將數據發送到小部件,您需要將數據寫入線程安全隊列,然後讓主線程輪詢該隊列(使用tkinter的after方法)。

+0

在這裏很好的參考:http://codeidol.com/python/python3/GUI-Coding-Techniques/GUIs,-Threads,-and-Queues/ – jdi 2012-08-05 23:20:07

+0

你們好棒。爲我的控制檯框架添加了一個隊列和一個方法,每隔10毫秒檢查隊列並將文本拖出。似乎現在工作得很好。謝謝。 – skandocious 2012-08-05 23:31:56