题
作为新的人GUI发展Python(与pyGTK),我刚开始学习穿线。测试我的能力,我已经写了一个简单的小GTK界面启动/停止的按钮。我们的目标是,当它被点击线开始,迅速增多的文字框中,同时保持GUI响应。
我已经得到了GUI工作就好了,但我有问题的穿线。它可能是一个简单的问题,但是我的心是关于炸的一天。下面我已经粘贴首先引用从蟒蛇口译员,然后由代码。你可以去 http://drop.io/pxgr5id 下载。我用开始修订控制,所以如果你想要做一个修改并重新放弃它,请提交更改。我也粘贴在代码 http://dpaste.com/113388/ 因为它可以有线号码,这个降价的东西是给我一个头痛的问题。
更新月27日,15时52EST:稍微更新的代码可以在这里找到: http://drop.io/threagui/asset/thread-gui-rev3-tar-gz
回溯
crashsystems@crashsystems-laptop:~/Desktop/thread-gui$ python threadgui.pybtnStartStop clicked
Traceback (most recent call last):
File "threadgui.py", line 39, in on_btnStartStop_clicked
self.thread.stop()
File "threadgui.py", line 20, in stop
self.join()
File "/usr/lib/python2.5/threading.py", line 583, in join
raise RuntimeError("cannot join thread before it is started")
RuntimeError: cannot join thread before it is started
btnStartStop clicked
threadStop = 1
btnStartStop clicked
threadStop = 0
btnStartStop clicked
Traceback (most recent call last):
File "threadgui.py", line 36, in on_btnStartStop_clicked
self.thread.start()
File "/usr/lib/python2.5/threading.py", line 434, in start
raise RuntimeError("thread already started")
RuntimeError: thread already started
btnExit clicked
exit() called
代码
#!/usr/bin/bash
import gtk, threading
class ThreadLooper (threading.Thread):
def __init__ (self, sleep_interval, function, args=[], kwargs={}):
threading.Thread.__init__(self)
self.sleep_interval = sleep_interval
self.function = function
self.args = args
self.kwargs = kwargs
self.finished = threading.Event()
def stop (self):
self.finished.set()
self.join()
def run (self):
while not self.finished.isSet():
self.finished.wait(self.sleep_interval)
self.function(*self.args, **self.kwargs)
class ThreadGUI:
# Define signals
def on_btnStartStop_clicked(self, widget, data=None):
print "btnStartStop clicked"
if(self.threadStop == 0):
self.threadStop = 1
self.thread.start()
else:
self.threadStop = 0
self.thread.stop()
print "threadStop = " + str(self.threadStop)
def on_btnMessageBox_clicked(self, widget, data=None):
print "btnMessageBox clicked"
self.lblMessage.set_text("This is a message!")
self.msgBox.show()
def on_btnExit_clicked(self, widget, data=None):
print "btnExit clicked"
self.exit()
def on_btnOk_clicked(self, widget, data=None):
print "btnOk clicked"
self.msgBox.hide()
def on_mainWindow_destroy(self, widget, data=None):
print "mainWindow destroyed!"
self.exit()
def exit(self):
print "exit() called"
self.threadStop = 1
gtk.main_quit()
def threadLoop(self):
# This will run in a thread
self.txtThreadView.set_text(str(self.threadCount))
print "hello world"
self.threadCount += 1
def __init__(self):
# Connect to the xml GUI file
builder = gtk.Builder()
builder.add_from_file("threadgui.xml")
# Connect to GUI widgets
self.mainWindow = builder.get_object("mainWindow")
self.txtThreadView = builder.get_object("txtThreadView")
self.btnStartStop = builder.get_object("btnStartStop")
self.msgBox = builder.get_object("msgBox")
self.btnMessageBox = builder.get_object("btnMessageBox")
self.btnExit = builder.get_object("btnExit")
self.lblMessage = builder.get_object("lblMessage")
self.btnOk = builder.get_object("btnOk")
# Connect the signals
builder.connect_signals(self)
# This global will be used for signaling the thread to stop.
self.threadStop = 1
# The thread
self.thread = ThreadLooper(0.1, self.threadLoop, (1,0,-1))
self.threadCounter = 0
if __name__ == "__main__":
# Start GUI instance
GUI = ThreadGUI()
GUI.mainWindow.show()
gtk.main()
解决方案
穿与PyGTK是棘手的如果你想要做的是正确的。基本上,你不应该更新GUI从内的任何其他线于主线(常见限制在GUI库).通常这样做是在PyGTK机构使用的排队的消息(之间的通信工作人员和GUI)读周期性地使用超时的功能。一旦我有一个介绍对我的当地耳关于这个主题,你可以抓住例如代码的介绍 谷歌码存储库.看看 MainWindow
类 forms/frmmain.py
, 专门用于方法 _pulse()
什么做 on_entry_activate()
(线开始有加空闲时器是创建)。
def on_entry_activate(self, entry):
text = entry.get_text().strip()
if text:
store = entry.get_completion().get_model()
if text not in [row[0] for row in store]:
store.append((text, ))
thread = threads.RecommendationsFetcher(text, self.queue)# <- 1
self.idle_timer = gobject.idle_add(self._pulse)# <- 2
tv_results = self.widgets.get_widget('tv_results')
model = tv_results.get_model()
model.clear()
thread.setDaemon(True)# <- 3
progress_update = self.widgets.get_widget('progress_update')
progress_update.show()
thread.start()# <- 4
这种方式、应用程序的更新GUI当是"空闲"(由GTK装置)没有造成冻结。
- 1:创建线
- 2:创建闲置计时器
- 3:agent线程,以便应用程序可以被关闭而无需等待线完成
- 4:启动线
其他提示
一般来说,最好避免线程时即可。这是非常难以正确地写一个线程的应用程序,并且更难以知道你是正确的。既然你写一个GUI应用程序,很容易让你想象如何这样做,因为你已经有一个异步的框架内编写应用程序。
要认识到的重要的一点是,一个GUI应用程序在做一大堆的什么都没有。它花费其大部分时间等待操作系统来告诉它有事情发生。你可以做很多的东西,在这个空闲时间,只要你知道如何编写长时间运行的代码,以便它不会阻止。
您可以通过使用一个超时解决你原有的问题;告诉您的GUI框架的延迟之后回叫一些函数,然后复位该延迟或启动另一个延迟呼叫。
另一个常见的问题是如何通过在GUI应用程序中的网络进行通信。网络应用程序是像GUI应用中,他们做了一大堆等待。使用网络的IO架构(如扭曲)可以很容易地让应用程序的两个部分等待合作,而不是竞争性,并再次减轻了对额外的线程的需要。
长期运行的计算可以迭代,而不是同步写入,你可以做你的处理,而GUI处于闲置状态。您可以使用一台发电机在Python做这个很容易。
def long_calculation(param, callback):
result = None
while True:
result = calculate_next_part(param, result)
if calculation_is_done(result):
break
else:
yield
callback(result)
调用long_calculation
会给你一台发电机的对象,并调用生成器对象上.next()
直到它到达或者yield
或return
将运行发电机。你只想告诉GUI框架调用long_calculation(some_param, some_callback).next
时候有时间,最终回调将结果进行调用。
我不知道GTK非常好,所以我不能告诉你哪个GObject的你应该调用函数。有了这个解释,但是,你应该能够找到所需功能的文档中,或在最坏的情况,请在相关的IRC频道。
不幸的是没有好一般情况下的答案。如果你与你想要做什么澄清,它会更容易解释为什么你不需要在这种情况下的线程。
您无法重新启动已停止的线程对象;不要尝试。相反,如果你想重新启动它后,它的真正停止,并加入创建对象的新实例。
我已经打了不同的工具来帮助清理与线程,闲置处理等工作。
make_idle是一个函数装饰,允许你在后台运行任务协作。这事很短,在UI线程中运行一次,并不会影响应用程序的响应性和在特殊的同步做了充分的出线程之间的良好的中间地带。内部装饰功能,使用“产量”,以回交出的处理图形用户界面,因此它可以保持响应,并在下一次UI空闲时会在你的功能,你离开了回暖。因此,为了得到这个开始你只需要调用idle_add的装饰功能。
def make_idler(func):
"""
Decorator that makes a generator-function into a function that will
continue execution on next call
"""
a = []
@functools.wraps(func)
def decorated_func(*args, **kwds):
if not a:
a.append(func(*args, **kwds))
try:
a[0].next()
return True
except StopIteration:
del a[:]
return False
return decorated_func
如果你需要做更多的处理,你可以使用上下文管理器锁定在UI线程在需要时帮助使代码更加安全。
@contextlib.contextmanager
def gtk_critical_section():
gtk.gdk.threads_enter()
try:
yield
finally:
gtk.gdk.threads_leave()
与您只需
with gtk_critical_section():
... processing ...
我还没有与它尚未完成,但在一个线程纯粹闲置和纯粹的结合做的事情,我有一个装饰(未测试却又如此没有发布),你可以告诉它的收益率之后的下一个部分是在UI中的空闲时间或者在一个线程中运行。这将允许人们做UI线程中的一些设置,切换到一个新的线程做背景的东西,然后切换到用户界面的空闲时间做清理工作,最大限度地减少对锁的需要。
我没有看过详细的代码。但是我看到了两个解决方案,您的问题:
不要使用线程的。代替使用超时,如下所示:
import gobject
i = 0
def do_print():
global i
print i
i += 1
if i == 10:
main_loop.quit()
return False
return True
main_loop = gobject.MainLoop()
gobject.timeout_add(250, do_print)
main_loop.run()
在使用线程,你必须确保你的GUI代码只从在同一时间一个线程调用镇守这样的:
import threading
import time
import gobject
import gtk
gtk.gdk.threads_init()
def run_thread():
for i in xrange(10):
time.sleep(0.25)
gtk.gdk.threads_enter()
# update the view here
gtk.gdk.threads_leave()
gtk.gdk.threads_enter()
main_loop.quit()
gtk.gdk.threads_leave()
t = threading.Thread(target=run_thread)
t.start()
main_loop = gobject.MainLoop()
main_loop.run()