初级Python线程问题

2024-03-29 04:48:34 发布

您现在位置:Python中文网/ 问答频道 /正文

作为Python中GUI开发的新手(使用pyGTK),我刚刚开始学习线程。为了测试我的技能,我编写了一个简单的带有启动/停止按钮的GTK界面。目标是,当单击它时,一个线程启动,在保持GUI响应的同时,快速增加文本框中的一个数字。

我让GUI工作得很好,但是线程有问题。这可能是一个简单的问题,但我的头脑是关于一天油炸。下面我首先粘贴了来自Python解释器的trackback,然后是代码。你可以去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()

Tags: inselfgetobjectondefbuildergui
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()将运行生成器,直到它到达yieldreturn。你只需要告诉GUI框架在有时间的时候调用long_calculation(some_param, some_callback).next,最终你的回调将被调用并得到结果。

我不太了解GTK,所以我不能告诉您应该调用哪个gobject函数。不过,有了这个解释,您应该能够在文档中找到必要的函数,或者最坏的情况下,在相关的IRC通道上询问。

不幸的是,没有很好的一般案例答案。如果你明确说明你想做什么,那么解释为什么在这种情况下不需要线程会更容易。

无法重新启动已停止的线程对象;请不要尝试。相反,如果要在对象真正停止并连接后重新启动它,请创建该对象的新实例。

如果您想正确地使用PyGTK,那么使用PyGTK进行线程处理有点棘手。基本上,您不应该从主线程以外的任何其他线程中更新GUI(GUI libs中的常见限制)。通常这是在PyGTK中使用排队消息机制(用于工人和GUI之间的通信)完成的,这些消息使用超时函数定期读取。一旦我有了关于这个主题的本地LUG的演示,您就可以从Google Code repository中获取这个演示的示例代码。看看forms/frmmain.py中的MainWindow类,特别是方法_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: 对线程进行守护,以便在不等待线程完成的情况下关闭应用程序
  • 4: 开始线程

相关问题 更多 >