2014-01-08 189 views
0

對於我的班級,我創建了一個「Mandelbrot Explorer」程序。有一個主要問題:在實際繪製到畫布上時,我失去了對GUI的控制(全部使用Python 2.7中的Tkinter/Ttk編寫)。Tkinter帆布凍結程序

這裏是我的代碼:

# There is some code above and below, but only this is relevant 

for real, imag in graph.PlaneIteration(self.graph.xMin, self.graph.xMax, resolution, self.graph.yMin, self.graph.yMax, resolution, master = self.graph, buffer_action = self.graph.flush): 
    # the above line iterates on the complex plane, updating the Canvas for every x value 
    c = complex(real, imag) 
    function, draw, z, current_iter = lambda z: z**2 + c, True, 0, 1 
    while current_iter <= iterations: 
     z = function(z) 
     if abs(z) > limit: 
      draw = False 
      break 
     current_iter += 1 
    self.progressbar.setValue(100 * (real + self.graph.xMax)/total) 
    color = self.scheme(c, current_iter, iterations, draw) 
    # returns a hex color value 
    self.graph.plot(c, color) 
    # self.graph is an instance of my custom class (ComplexGraph) which is a wrapper 
    # around the Canvas widget 
    # self.graph.plot just creates a line on the Canvas: 
    # self.create_line(xs,ys,xs+1,ys+1, fill=color) 

我的問題是,在運行時,該圖形需要一段時間 - 大約30秒。在這個時候,我不能使用GUI。如果我嘗試,一旦完成繪圖,窗口就會凍結並且只會解凍。

我試圖使用線程(I所包圍的上部代碼的整體中的功能,thread_process):

thread.start_new_thread(thread_process,()) 

然而,問題依然存在。

有沒有辦法解決這個問題?謝謝!

+0

如果你想產生一個新的線程,你的線程應該繪製到某個地方的數組或圖像對象,而不是GUI上的畫布對象。然後,它可以返回可以在畫布上繪製的圖像。線程運行時,它不會以這種方式影響GUI。 –

+0

@ChrisBarker我曾考慮過這樣做,但我想保留它的動畫(因爲它每列更新)。如果涉及到它,我將採用繪製到圖像,然後將圖像添加到畫布上,但現在我想避免這種情況:P。 –

回答

2

你可以用Tkinter執行你的「線程化」循環,在繪製每個點後隱式返回Tkinter的主循環執行。通過使用widget.after註冊下一個函數調用做到這一點:

plane = graph.PlaneIteration(...) 
def plotNextPoint(): 
    try: 
     real, imag = plane.next() 
    except StopIteration: 
     return 
    c = complex(real, imag) 
    ... 
    self.graph.plot(c, color) 
    self.graph.after(0, plotNextPoint) 
plotNextPoint() 

這樣,您繪製每個點之後,Tkinter的主循環將再次運行,並再次調用您的plotNextPoint函數之前更新顯示。如果這太慢,請嘗試在for _ in xrange(n)循環中將plotNextPoint正文包裝在n之間,以便在重繪之間繪製n點。

+0

這裏你並不需要'1'(除非你試圖將事情降低到1000fps或更低......)。但除此之外,回調解決方案的總結很好。 – abarnert

+0

@abarnert好點;將其更改爲0。 –

1

你是對的問題的原因 - 當你忙於運行這段代碼時,GUI事件循環沒有運行。

你說得對,線程是一個很好的解決方案。 (另一個主要的解決方案是將工作分解爲更小的子任務,並且每個工作都安排下一個工作,關於選項和所有皺紋的更詳細的概述,請參閱Why your GUI app freezes。)

但它不是那麼簡單把整個事情放在一個線程上。不幸的是,Tkinter(與許多GUI框架一樣)不是自由線程的。您無法從後臺線程調用任何GUI對象的方法。如果你這樣做,不同的事情會發生在不同的平臺和版本上,從阻塞主線程到崩潰程序到引發異常。另外,請記住,即使沒有Tkinter,也不能在線程之間安全地共享可變對象,而無需進行某種同步。而你正在用Tkinter對象來做那件事,對吧?

的Tkinter的維基解釋了一個辦法來解決這兩個問題,在一次Tkinter and Threads:創建一個Queue,有後臺線程put的消息就可以了,並有主線程檢查它每隔一段時間(例如,通過使用after安排一個非阻塞get每100ms直到後臺線程完成)。

如果您不想提出將協議從後臺線程傳遞到主線程的「協議」,請記住在Python中,綁定方法或綁定方法的元組以及一些參數,它非常好,可傳遞數據。因此,不要致電self.graph.plot(c, color),而只需self.q.put((self.graph.plot, c, color))

圖書館mtTkinter包裝這一切爲你,使它看起來像Tkinter是通過在後臺使用隊列的自由線程。它沒有經過高度測試或經常維護,但即使它在將來不起作用,它仍然會產生很好的示例代碼。

+0

@ F3AR3DLEGEND:差不多,但不完全;它_wraps_ Tkinter而不是monkeypatching它。所以你做它,而不是你正常的Tkinter導入。如果您通常從'Tkinter import *'執行'',請執行'from mtTkinter import *';如果你通常做'導入Tkinter',那麼'導入mtTkinter作爲Tkinter'。 – abarnert

+0

這對我有用,但我有一個問題---我以前的設置在每個x值之後調用'self.graph.update_idletasks()'(這是在我的'graph.PlaneIteration'迭代器中自動執行的) 。但是,使用線程時,它會忽略這一點並更新每個點。這顯着降低了繪圖速度(超過10倍)。我認爲這是循環和線程的直接問題。我能解決這個問題嗎? –

+0

@ F3AR3DLEGEND:另外,我相信默認的隊列檢查間隔是6或10fps;如果你想更頻繁地看到更新,那麼你需要配置一個變量,這在我看過的章節中有解釋。 – abarnert