在长时间运行的过程中阻止pygtk GUI锁定

时间:2011-12-21 01:07:01

标签: python multithreading pygtk

我的流程需要一段时间(可能是一两分钟)才能完成。当我从pygtk GUI调用此窗口时,窗口会在大约10秒后锁定(变暗并阻止用户操作)。

我想阻止这种情况发生,但我不确定如何发生。我认为多线程将是答案,但似乎并没有起作用。我尝试了两种在网上找到的不同方法。首先,我修改了this FAQ以执行长时间运行的功能。其次我尝试使用threading.Thread直接像this answer一样,但也锁定了。

我的两个样本如下。我是多线程的新手,所以也许这不是我正在寻找的解决方案。我基本上只是试图阻止GUI锁定,所以我可以使用进度条进行更新,让用户使用取消按钮。

#Sample 1
import threading
import time
import gobject
import gtk

gobject.threads_init()

class MyThread(threading.Thread):
    def __init__(self, label, button):
        super(MyThread, self).__init__()
        self.label = label
        self.button = button
        self.counter = 0
        button.connect("clicked", self.on_button_click)
        self.quit = False

    def update_label(self, counter):
        self.label.set_text("Counter: %i" % counter)
        time.sleep(20)
        return False

    def on_button_click(self, widget):
        self.counter += 1
        gobject.idle_add(self.update_label, self.counter)

window = gtk.Window()
label = gtk.Label()
box = gtk.VBox()
button = gtk.Button("Test")
box.pack_start(label)
box.pack_start(button)
window.add(box)
window.show_all()
window.connect("destroy", lambda _: gtk.main_quit())
thread = MyThread(label, button)
thread.start()

gtk.main()
thread.quit = True

#####################################
#Sample 2

from threading import Thread
import time
import gobject
import gtk

class Test():
    def __init__(self):
        self.counter = 0
        self.label = gtk.Label()
        button = gtk.Button("Test")

        window = gtk.Window()
        box = gtk.VBox()
        box.pack_start(self.label)
        box.pack_start(button)
        window.add(box)

        window.connect("destroy", lambda _: gtk.main_quit())
        button.connect("clicked", self.on_button_click)
        window.show_all()

    def update_label(self, counter):
        self.label.set_text("Counter: %i" % counter)
        time.sleep(20)
        return False

    def on_button_click(self, widget):
        self.counter += 1
        thread = Thread(target=self.update_label, args=(self.counter,))
        thread.start()
        while thread.is_alive():
            pass
        thread.stop()

test = Test()
gtk.main()

2 个答案:

答案 0 :(得分:7)

请在下面找到适用于我的第二个示例的修改版本:

import threading
import time
import gtk, gobject, glib

gobject.threads_init()

class Test():
    def __init__(self):
        self.counter = 0
        self.label = gtk.Label()
        self.progress_bar = gtk.ProgressBar()
        self.progress_bar_lock = threading.Lock()
        button = gtk.Button("Test")

        window = gtk.Window()

        box = gtk.VBox()
        box.pack_start(self.label)
        box.pack_start(self.progress_bar)
        box.pack_start(button)
        window.add(box)

        window.connect("destroy", lambda _: gtk.main_quit())
        button.connect("clicked", self.on_button_click)
        window.show_all()

    def update_label(self, counter):
        self.label.set_text("Thread started (counter: {0})"
                            .format(counter))
        time.sleep(5)
        self.label.set_text("Thread finished (counter: {0})"
                            .format(counter))
        return False

    def pulse_progress_bar(self):
        print threading.active_count()
        if threading.active_count() > 1:
            self.progress_bar.pulse()
            return True

        self.progress_bar.set_fraction(0.0)
        self.progress_bar_lock.release()
        return False

    def on_button_click(self, widget):
        self.counter += 1
        thread = threading.Thread(target=self.update_label,
                                  args=(self.counter,))
        thread.start()

        if self.progress_bar_lock.acquire(False):
            glib.timeout_add(250, self.pulse_progress_bar)


if __name__ == '__main__':
    test = Test()
    gtk.main()

所做的更改是:

  • 避免在回调中等待线程完成以保持主循环处理事件。
  • 添加了进度条,以便在执行线程时显示。
  • 使用glib.timeout_add来安排在执行某个线程时脉冲进度条的回调。这与轮询线程具有相同的效果,但主要优点是主循环仍然响应其他事件。
  • 使用threading.Lock来阻止多次调度回调,无论点击按钮多少次。
  • 添加了此示例中缺少的gobject.threads_init(不在上一个示例中)。

现在,当点击按钮时,只要线程正在运行,您就会看到如何点击标签并且进度条会发出脉冲。

答案 1 :(得分:0)

您应该为每个线程重新实现Thread.run,并在其中启动一个事件循环。

此外,你可以按下按钮调用一个线程的start方法,然后调用run,完成你的长任务。这样,您就不需要在每个线程中使用事件循环。

这是一些简单的代码来解释我对第二个选项的意思:

class MyThread(threading.Thread):

    def __init__(self, label, button):
        threading.Thread.__init__(self)
        self.label = label
        self.button = button
        self.counter = 0

    def run(self):
        time.sleep(20)

def callback():
    label.set_text("Counter: %i" % thread.counter)
    thread.start()

window = gtk.Window()
label = gtk.Label()
box = gtk.VBox()
button = gtk.Button('Test')
box.pack_start(label)
box.pack_start(button)
window.add(box)
window.show_all()

thread = MyThread(label, button)
button.connect('clicked', callback)

我使用回调函数,因为我怀疑set_text是线程安全的。