Остановить блокировку графического интерфейса pygtk во время длительного процесса - PullRequest
5 голосов
/ 21 декабря 2011

У меня есть процесс, который займет некоторое время (возможно, минуту или две), чтобы завершить. Когда я вызываю это через мой графический интерфейс pygtk, окно блокируется (темнеет и предотвращает действия пользователя) примерно через 10 секунд.

Я бы хотел, чтобы это не случилось, но я не уверен, как это сделать. Я думал, что многопоточность будет ответом, но, похоже, не работает. Я пробовал два разных метода, которые я нашел в Интернете. Во-первых, я изменил этот FAQ , чтобы он работал долго. Во-вторых, я попытался использовать многопоточность. Напишите прямо как в этот ответ , но он также блокируется.

Мои два образца ниже. Я новичок в многопоточности, так что, возможно, это не то решение, которое я ищу. Я просто пытаюсь не допустить блокировки графического интерфейса, чтобы я мог обновлять его с помощью индикатора выполнения и позволить пользователю использовать кнопку отмены.

#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 ]

7 голосов
/ 21 декабря 2011

Ниже приведена модифицированная версия второго примера, который работает для меня:

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, который отсутствовал в этом примере (не в предыдущем).

Теперь, нажимая на кнопку, вы увидите, как нажимается метка, а индикатор выполнения пульсирует, пока работает поток.

0 голосов
/ 21 декабря 2011

Вы должны переопределить 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 является потокобезопасным.

...