2013-04-05 42 views
1

我在學習如何使用線程模塊。我跟着一起在這裏說明:http://effbot.org/zone/tkinter-threads.htm創建線程安全的Toplevel小部件

我希望測試腳本:

  1. 打印出來的「計數」每兩秒鐘
  2. 顯示彈出對話窗口(也每隔2秒)
  3. 的彈出窗口,應允許積累(如果我不點擊「OK」了一段時間,應該有 多個彈出窗口)

Howev呃,當我運行這個腳本時,它會凍結主窗口,並在一段時間後崩潰。我想我沒有正確實現線程模塊。

有人請看看並指出我做錯了什麼?

這是我到目前爲止已經試過:

from Tkinter import * 
import thread 
import Queue 
import time 

class TestApp: 
    def __init__(self, parent): 
     self.super_Parent = parent 
     self.main_container = Frame(parent) 
     self.main_container.pack() 
     self.top_frame = Frame(self.main_container) 
     self.top_frame.pack(side=TOP) 
     self.bottom_frame = Frame(self.main_container) 
     self.bottom_frame.pack(side=TOP) 
     self.text_box = Text(self.top_frame) 
     self.text_box.config(height=20, width=20) 
     self.text_box.pack() 
     self.queue = Queue.Queue() 
     self.update_me() 

    def show_popup(self): 
     self.my_popup = Toplevel(self.main_container) 
     self.my_popup.geometry('100x100') 
     self.popup_label = Label(self.my_popup, text="Hello!") 
     self.popup_label.pack(side=TOP) 
     self.pop_button = Button(self.my_popup, text="OK", command=self.my_popup.destroy) 
     self.pop_button.pack(side=TOP) 

    def write(self, line): 
     self.queue.put(line) 

    def update_me(self): 
     try: 
      while 1: 
       line = self.queue.get_nowait() 
       if line is None: 
        self.text_box.delete(1.0, END) 
       else: 
        self.text_box.insert(END, str(line)) 
       self.text_box.see(END) 
       self.text_box.update_idletasks() 
     except Queue.Empty: 
      pass 
     self.text_box.after(100, self.update_me) 

def pipeToWidget(input, widget): 
    widget.write(input) 

def start_thread(): 
    thread.start_new(start_test, (widget,)) 

def start_test(widget): 
    count = 0 
    while True: 
     pipeToWidget(str(count) + "\n", widget) 
     count += 1 
     time.sleep(2) 
     widget.show_popup() 

root = Tk() 
widget = TestApp(root) 
start_button = Button(widget.bottom_frame, command=start_thread) 
start_button.configure(text="Start Test") 
start_button.pack(side=LEFT) 
root.title("Testing Thread Module") 
root.mainloop() 
+0

首先,作爲['thread'](http://docs.python.org/2/library/thread.html)文檔反覆就是說,['threading'](HTTP://docs.python。 org/3/library/thread.html)模塊「提供了一個更易於使用和更高級別的API」,您應該強烈考慮使用它。 – abarnert 2013-04-05 22:28:55

+0

同時......你確定這正是你正在運行的代碼嗎?當我運行它時(OS X 10.8.3與64位Apple Python 2.7.2),它似乎完全按照描述工作,沒有凍結或崩潰。 – abarnert 2013-04-05 22:31:07

+0

@abarnert好的,我會閱讀線程。另外,是的,我正在運行Win 7上的確切代碼Python 2.7.3 – 2013-04-05 22:38:02

回答

3

我無法重現你的問題,但我可以看到,爲什麼會發生這種事。

您正在使用queue將消息從後臺線程傳遞到主線程以更新text_box,這是正確的。但是您也從後臺線程調用widget.show_popup(),這意味着它會在後臺線程中創建並顯示新的Toplevel。那是不是是正確的。

所有用戶界面代碼必須運行在相同的線程 - 並非所有UI代碼爲每個頂級窗口,所有UI代碼期間。在某些平臺上,你可能會在自己的線程中運行每個窗口(甚至是自由線程化所有內容),但這不是假設可以工作,並且肯定會在某些平臺上崩潰或做不正確的事情。 (同樣,單UI線程必須在某些平臺上的初始線程,但在這裏不相關)。


因此,要解決這個問題,你需要做同樣的舞蹈用於創建彈出窗口你爲更新文本框做了什麼。

明顯的做法是將widget.show_popup()移動到update_me()的循環中。如果您希望在文本框更新後2秒內發生,請將self.top_frame.after(2000, self.show_popup)添加到該方法。

但我猜你正試圖教自己如何有多個獨立的更新機制,所以告訴你「只用一個更新隊列的一切」可能不是一個好的答案。在這種情況下,只需創建兩個隊列,併爲每個隊列提供單獨的更新方法。然後,做你的pipeToWidget,睡2秒,然後pipeToPopup


另一種解決方法是使用mtTkinter。它基本上完成了你正在做的工作,但是它會自動完成,將每個實際的Tk GUI調用推入隊列,稍後由主循環運行。當然,你的對象本身必須是線程安全的,這也意味着你必須處理來自一個線程的GUI調用與另一個線程的調用交錯。但只要這兩者都不是問題(而且它們似乎不屬於你的情況),這就像是魔法。


如果你想知道爲什麼這是在OS X 10.8凍結和/或Win7上崩潰了你,而不是我...好了,你真的需要考慮的Tcl,C亂七八糟的,和Python代碼,以及每個東西的構建方式。而且,除非它是簡單的東西(比如你的Tk版本不是免費線程的),反正它不會告訴你什麼。該代碼不應該工作,如果它似乎適用於我......這可能意味着它將每次都工作,直到我職業生涯中最重要的演示,在這一點上它會失敗。

+0

是的,我試圖圍繞線程(ing)纏繞我的頭,這對我來說是一個新概念。謝謝你的解釋。將它移動到update_me()中的循環使其工作。 :) – 2013-04-05 22:53:10

+0

很酷。我也會看看mtTkinter。再次感謝。 – 2013-04-05 22:56:58

+0

@DirtyPenguin:如果你不確定每個代碼運行哪個線程,有一個簡單的方法來檢查:在構建時給每個新的'threading.Thread'命名,並添加一行來打印'threading .current_thread().name'和你要在每個Tk調用之前調用的內容。然後,你會看到「背景:關於調用Toplevel()',你會知道,無論出於什麼原因,你是從錯誤的線程來做它。 – abarnert 2013-04-05 23:03:16