初级Python线程问题
作为 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()
As someone new to GUI development in Python (with pyGTK), I've just started learning about threading. To test out my skills, I've written a simple little GTK interface with a start/stop button. The goal is that when it is clicked, a thread starts that quickly increments a number in the text box, while keeping the GUI responsive.
I've got the GUI working just fine, but am having problems with the threading. It is probably a simple problem, but my mind is about fried for the day. Below I have pasted first the trackback from the Python interpreter, followed by the code. You can go to http://drop.io/pxgr5id to download it. I'm using bzr for revision control, so if you want to make a modification and re-drop it, please commit the changes. I'm also pasting the code at http://dpaste.com/113388/ because it can have line numbers, and this markdown stuff is giving me a headache.
Update 27 January, 15:52 EST:
Slightly updated code can be found here: http://drop.io/threagui/asset/thread-gui-rev3-tar-gz
Traceback
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
Code
#!/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()
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
如果您想正确执行,使用 PyGTK 进行线程化有点棘手。 基本上,您不应该从主线程以外的任何其他线程内更新 GUI(GUI 库中的常见限制)。 通常,这是在 PyGTK 中使用排队消息机制(用于工作程序和 GUI 之间的通信)来完成的,这些消息使用超时函数定期读取。 当我在本地 LUG 上就该主题进行演示后,您可以从 Google 代码获取此演示的示例代码存储库。 查看
forms/frmmain.py
中的MainWindow
类,特别是方法_pulse()
以及on_entry_activate( )
(线程在那里启动,并创建空闲计时器)。这样,应用程序在“空闲”(通过 GTK 方式)时更新 GUI,不会导致冻结。
Threading with PyGTK is bit tricky if you want to do it right. Basically, you should not update GUI from within any other thread than main thread (common limitation in GUI libs). Usually this is done in PyGTK using mechanism of queued messages (for communication between workers and GUI) which are read periodically using timeout function. Once I had a presentation on my local LUG on this topic, you can grab example code for this presentation from Google Code repository. Have a look at
MainWindow
class informs/frmmain.py
, specially for method_pulse()
and what is done inon_entry_activate()
(thread is started there plus the idle timer is created).This way, application updates GUI when is "idle" (by GTK means) causing no freezes.
一般来说,最好尽可能避免使用线程。 正确编写线程应用程序非常困难,更难知道自己是否正确。 由于您正在编写 GUI 应用程序,因此您可以更轻松地想象如何执行此操作,因为您已经必须在异步框架中编写应用程序。
需要认识到的重要一点是 GUI 应用程序没有做很多事情。 它大部分时间都在等待操作系统告诉它发生了什么事。 只要您知道如何编写长时间运行的代码,使其不会阻塞,您就可以在空闲时间做很多事情。
你可以通过使用超时来解决你原来的问题; 告诉你的 GUI 框架在延迟后回调某个函数,然后重置该延迟或启动另一个延迟调用。
另一个常见问题是如何在 GUI 应用程序中通过网络进行通信。 网络应用程序与 GUI 应用程序类似,它们需要大量等待。 使用网络 IO 框架(例如 Twisted)可以轻松地让应用程序的两个部分合作等待,而不是竞争等待,并再次减少了对额外线程的需求。
可以迭代而不是同步地编写长时间运行的计算,并且您可以在 GUI 空闲时进行处理。 您可以使用生成器在 python 中轻松完成此操作。
调用
long_calculation
将为您提供一个生成器对象,并且在生成器对象上调用.next()
将运行生成器,直到它达到yield
或返回
。 您只需告诉 GUI 框架在有时间时调用long_calculation(some_param, some_callback).next
,最终将使用结果调用您的回调。我不太了解 GTK,所以我无法告诉您应该调用哪些 gobject 函数。 不过,有了这个解释,您应该能够在文档中找到必要的功能,或者最坏的情况是在相关的 IRC 频道上询问。
不幸的是,没有好的一般情况答案。 如果您准确地阐明了您想要做什么,那么会更容易解释为什么在这种情况下不需要线程。
Generally it's better to avoid threads when you can. It's very difficult to write a threaded application correctly, and even more difficult to know you got it right. Since you're writing a GUI application, it's easier for you to visualize how to do so, since you already have to write your application within an asynchronous framework.
The important thing to realize is that a GUI application is doing a whole lot of nothing. It spends most of its time waiting for the OS to tell it that something has happened. You can do a lot of stuff in this idle time as long as you know how to write long-running code so it doesn't block.
You can solve your original problem by using a timeout; telling your GUI framework to call back some function after a delay, and then resetting that delay or starting another delayed call.
Another common question is how to communicate over the network in a GUI application. Network apps are like GUI apps in that they do a whole lot of waiting. Using a network IO framework (like Twisted) makes it easy to have both parts of your application wait cooperatively instead of competitively, and again alleviates the need for extra threads.
Long-running calculations can be written iteratively instead of synchronously, and you can do your processing while the GUI is idle. You can use a generator to do this quite easily in python.
Calling
long_calculation
will give you a generator object, and calling.next()
on the generator object will run the generator until it reaches eitheryield
orreturn
. You would just tell the GUI framework to calllong_calculation(some_param, some_callback).next
when it has time, and eventually your callback will be called with the result.I don't know GTK very well, so I can't tell you which gobject functions you should be calling. With this explanation, though, you should be able to find the necessary functions in the documentation, or at worst, ask on a relevant IRC channel.
Unfortunately there is no good general-case answer. If you clarify with exactly what you're trying to do, it would be easier to explain why you don't need threads in that situation.
您无法重新启动已停止的线程对象; 不要尝试。 相反,如果您想在对象真正停止并加入后重新启动它,请创建对象的新实例。
You can't restart a stopped thread object; don't try. Instead, create a new instance of the object if you want to restart it after it's truly stopped and joined.
我使用过不同的工具来帮助清理线程、空闲处理等工作。
make_idle 是一个函数装饰器,允许您在后台协作运行任务。 这是一个很好的中间立场,介于足够短以在 UI 线程中运行一次并且不影响应用程序的响应能力和在特殊同步中执行完整线程之间。 在装饰函数中,您使用“yield”将处理交还给 GUI,以便它可以保持响应,并且下次 UI 空闲时,它将在您上次中断的函数中继续执行。 因此,要开始此操作,您只需调用idle_add 到装饰函数即可。
如果你需要做更多的处理,你可以使用上下文管理器在需要时锁定 UI 线程,以帮助使代码更安全一点,
你可以只是
我还没有完成它,但纯粹结合做事在空闲且纯粹在线程中,我有一个装饰器(尚未测试,因此未发布),您可以告诉它yield之后的下一部分是在UI的空闲时间还是在线程中运行。 这将允许用户在 UI 线程中进行一些设置,切换到一个新线程来执行后台操作,然后切换到 UI 的空闲时间来进行清理,从而最大限度地减少对锁的需求。
I've played with different tools to help clean up the work with threads, idle processing, etc.
make_idle is a function decorator that allows you to run a task in the background cooperatively. This is a good middle ground between something short enough to run once in the UI thread and not affect the responsiveness of the app and doing a full out thread in special synchronization. Inside the decorated function you use "yield" to hand the processing back over to the GUI so it can remain responsive and the next time the UI is idle it will pick up in your function where you left off. So to get this started you just call idle_add to the decorated function.
If you need to do a bit more processing, you can use a context manager to lock the UI thread whenever needed to help make the code a bit safer
with that you can just
I have not finished with it yet, but in combining doing things purely in idle and purely in a thread, I have a decorator (not tested yet so not posted) that you can tell it whether the next section after the yield is to be run in the UI's idle time or in a thread. This would allow one to do some setup in the UI thread, switch to a new thread for doing background stuff, and then switch over to the UI's idle time to do cleanup, minimizing the need for locks.
我没有详细查看你的代码。 但我看到您的问题有两种解决方案:
根本不使用线程。 相反,请使用超时,如下所示:
当使用线程时,您必须通过如下保护来确保您的 GUI 代码仅同时从一个线程调用:
I haven't looked in detail on your code. But I see two solutions to your problem:
Don't use threads at all. Instead use a timeout, like this:
When using threads, you must make sure that your GUI code is only called from one thread at the same time by guarding it like this: