作为Python的GUI开发新手(使用pyGTK),我刚开始学习线程。为了测试我的技能,我写了一个简单的小GTK界面和一个开始/停止按钮。目标是,当单击它时,一个线程启动,快速增加文本框中的数字,同时保持GUI响应。
我的GUI工作得很好,但是我遇到了线程问题。这可能是一个简单的问题,但我的想法是关于当天的油炸。下面我首先粘贴了Python解释器的引用,然后是代码。您可以转到http://drop.io/pxgr5id下载它。我正在使用bzr进行版本控制,因此如果您想进行修改并重新删除它,请提交更改。我也在http://dpaste.com/113388/粘贴代码,因为它可以有行号,这个降价的东西让我很头疼。
美国东部时间1月27日15:52更新: 可以在此处找到稍微更新的代码: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()
答案 0 :(得分:9)
如果你想做正确的话,使用PyGTK进行线程攻击有点棘手。基本上,您不应该从主线程之外的任何其他线程中更新GUI(GUI库中的常见限制)。通常这是在PyGTK中使用排队消息的机制(用于工作者和GUI之间的通信)来完成的,这些机制使用超时功能定期读取。一旦我在本地LUG上有关于此主题的演示文稿,您就可以从Google Code repository获取此演示文稿的示例代码。查看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
这样,应用程序在“空闲”时(通过GTK表示)更新GUI,不会造成冻结。
答案 1 :(得分:3)
通常情况下,尽可能避免使用线程。正确编写线程应用程序非常困难,甚至更难以确定您是否正确。由于您正在编写GUI应用程序,因此您可以更轻松地查看如何执行此操作,因为您必须在异步框架中编写应用程序。
要实现的重要一点是GUI应用程序正在做很多事情。它花费大部分时间等待操作系统告诉它发生了什么事。只要你知道如何编写长时间运行的代码就可以在这个空闲时间做很多事情,这样就不会阻塞它。
您可以使用超时解决原始问题;告诉你的GUI框架在延迟后回调某个函数,然后重置该延迟或开始另一个延迟调用。
另一个常见问题是如何在GUI应用程序中通过网络进行通信。网络应用程序就像GUI应用程序,因为它们需要做很多等待。使用网络IO框架(如Twisted)可以轻松地让应用程序的两个部分协同工作而不是竞争性,并再次减少对额外线程的需求。
长时间运行的计算可以迭代编写而不是同步编写,您可以在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频道上查询。
不幸的是,没有好的一般案例答案。如果你准确地澄清了你想要做的事情,那么解释为什么你不需要线程就更容易解释。
答案 2 :(得分:1)
您无法重新启动已停止的线程对象;别试试。相反,如果要在真正停止并加入后重新启动它,请创建该对象的新实例。
答案 3 :(得分:0)
我玩过不同的工具来帮助清理线程,空闲处理等工作。
make_idle是一个函数装饰器,允许您在后台协同运行任务。这是一个很好的中间点,可以在UI线程中运行一次,并且不会影响应用程序的响应性并在特殊同步中执行完整的线程。在装饰函数内部,您使用“yield”将处理交还给GUI,以便它可以保持响应,下次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 ...
我还没有完成它,但是在结合纯粹处于空闲状态并纯粹在一个线程中做事时,我有一个装饰器(尚未测试,所以没有发布),你可以告诉它是否在yield之后的下一部分是在UI的空闲时间或线程中运行。这将允许人们在UI线程中进行一些设置,切换到新线程来执行后台操作,然后切换到UI的空闲时间进行清理,最大限度地减少对锁的需求。
答案 4 :(得分:0)
我没有仔细查看您的代码。但是我看到了两个问题的解决方案:
根本不要使用线程。而是使用超时,如下所示:
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()