对于我的班级,我正在创建一个“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, ())
然而,问题仍然存在。
有没有办法解决这个问题?谢谢!
答案 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一样是自由线程的。它没有经过高度测试或经常维护,但即使它未来不起作用,它仍然可以提供很好的示例代码。