Tkinter Canvas冻结程序

时间:2014-01-08 00:46:09

标签: python canvas tkinter

对于我的班级,我正在创建一个“Mandelbrot Explorer”程序。有一个主要问题:当实际绘制到Canvas时,我失去了对GUI的控制(全部用Tkinter / Ttk编写,在Python 2.7中)。

这是我的代码:

# 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。如果我尝试,窗口会冻结,只有在完成绘图后才会解冻。

我尝试使用线程(我在函数thread_process中包含了整个高层代码):

thread.start_new_thread(thread_process, ())

然而,问题仍然存在。

有没有办法解决这个问题?谢谢!

2 个答案:

答案 0 :(得分: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函数之前更新显示。如果这太慢,请尝试在plotNextPoint循环中包裹for _ in xrange(n)的正文,以在重绘之间绘制n点。

答案 1 :(得分:1)

您对问题的原因是正确的 - 当您忙于运行此代码时,GUI事件循环未运行。

你认为线程是一个很好的解决方案。 (另一个主要的解决方案是将工作分解为更小的子任务,并让每个子任务安排下一个。有关选项和所有皱纹的更详细概述,请参阅Why your GUI app freezes。)

但它并不像把整个事情放在一个线程那么简单。

不幸的是,Tkinter(与许多GUI框架一样)不是自由线程的。您无法从后台线程调用任何GUI对象上的方法。如果这样做,不同的平台和版本会发生不同的事情,从阻止主线程到崩溃程序到引发异常。

另外,请记住,即使没有Tkinter,也无法在线程之间安全地共享可变对象,而无需进行某种同步。并且你正在使用Tkinter对象做到这一点,对吧?

Tkinter wiki解释了在Tkinter and Threads中同时解决这两个问题的一种方法:创建一个Queue,在其上有背景线程put消息,并拥有主要内容线程经常检查它(例如,通过使用after每隔100毫秒安排一次非阻塞get,直到完成后台线程。)

如果您不想提出&#34;协议&#34;为了将数据从后台线程传递到主线程,请记住在Python中,绑定方法或绑定方法的元组和一些参数,它是非常好的,可通过的数据。因此,您只需self.graph.plot(c, color)

,而不是致电self.q.put((self.graph.plot, c, color))

mtTkinter为你完成了这一切,让它看起来就像Tkinter在后台使用Queue一样是自由线程的。它没有经过高度测试或经常维护,但即使它未来不起作用,它仍然可以提供很好的示例代码。