Pregunta

Como alguien nuevo desarrollo de GUI en Python (con pyGTK), acabo de empezar a aprender acerca de las operaciones de roscado.Para poner a prueba mis habilidades, he escrito un pequeño y simple interfaz GTK con un botón start/stop.El objetivo es que cuando se hace clic en un subproceso se inicia rápidamente incrementos de un número en el cuadro de texto, manteniendo la interfaz de usuario sensible.

Tengo la interfaz gráfica de usuario trabajando muy bien, pero estoy teniendo problemas con el hilo.Es probable que sea un problema simple, pero mi mente está sobre frito para el día.A continuación he copiado primero la trackback desde el intérprete de Python, seguido por el código.Usted puede ir a http://drop.io/pxgr5id para descargarlo.Estoy usando bzr para el control de revisión, por lo que si usted desea hacer una modificación y volver a caer, por favor confirmar los cambios.Yo también soy de pegar el código en http://dpaste.com/113388/ porque puede tener números de línea, y esta de rebajas cosas me está dando un dolor de cabeza.

Actualización 27 de enero, 15:52 EST:Ligeramente actualizada de código se puede encontrar aquí: http://drop.io/threagui/asset/thread-gui-rev3-tar-gz

Sobre la procedencia del producto

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

Código

#!/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()
¿Fue útil?

Solución

Roscado con PyGTK es un poco difícil si usted quiere hacerlo bien.Básicamente, usted no debe actualización de la GUI en cualquier otro hilo que el hilo principal (común limitación en la GUI libs).Normalmente esto se hace en PyGTK utilizando el mecanismo de cola de mensajes (para la comunicación entre los trabajadores y la interfaz gráfica de usuario) que se leen periódicamente utilizando la función de tiempo de espera.Una vez tuve una presentación en mi local LUG sobre este tema, se puede tomar el ejemplo de código para esta presentación de Google repositorio de Código.Eche un vistazo a MainWindow clase en forms/frmmain.py, especialmente para el método _pulse() y lo que se hace en on_entry_activate() (subproceso se inicia, más el temporizador de inactividad es creado).

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

De esta manera, las actualizaciones de la aplicación GUI cuando es "inactivo" (por GTK medios) de la causa no se congela.

  • 1:crear el hilo
  • 2:crear temporizador de inactividad
  • 3:daemonize hilo de modo que la aplicación puede ser cerrado sin esperar a que el hilo de la finalización
  • 4:inicio del hilo

Otros consejos

En general, es mejor evitar hilos cuando puedas. Es muy difícil escribir una aplicación enhebrada correctamente, y aún más difícil saber que está bien. Como está escribiendo una aplicación GUI, es más fácil para usted visualizar cómo hacerlo, ya que ya tiene que escribir su aplicación dentro de un marco asíncrono.

Lo importante es darse cuenta de que una aplicación GUI no está haciendo nada. Pasa la mayor parte del tiempo esperando que el sistema operativo le diga que algo ha sucedido. Puede hacer muchas cosas en este tiempo de inactividad siempre que sepa cómo escribir código de ejecución prolongada para que no se bloquee.

Puede resolver su problema original utilizando un tiempo de espera; diciéndole a su marco GUI que vuelva a llamar alguna función después de un retraso, y luego reinicie ese retraso o comience otra llamada retrasada.

Otra pregunta común es cómo comunicarse a través de la red en una aplicación GUI. Las aplicaciones de red son como las aplicaciones GUI, ya que esperan mucho. El uso de un marco de red de E / S (como Twisted ) facilita que ambas partes de su aplicación esperen de forma cooperativa en lugar de competitiva, y nuevamente alivia la necesidad de hilos adicionales.

Los cálculos de larga duración se pueden escribir de forma iterativa en lugar de sincronizada, y puede realizar su procesamiento mientras la GUI está inactiva. Puede usar un generador para hacer esto con bastante facilidad en 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)

Llamar a long_calculation le dará un objeto generador, y llamar a .next() en el objeto generador ejecutará el generador hasta que llegue a yield o return. Simplemente le dirá al marco de la GUI que llame a long_calculation(some_param, some_callback).next cuando tenga tiempo y, finalmente, se llamará a su devolución de llamada con el resultado.

No conozco GTK muy bien, así que no puedo decirte qué funciones de objeto deberías llamar. Sin embargo, con esta explicación, debería poder encontrar las funciones necesarias en la documentación o, en el peor de los casos, preguntar en un canal IRC relevante.

Desafortunadamente no hay una buena respuesta de caso general. Si aclara exactamente lo que está tratando de hacer, sería más fácil explicar por qué no necesita hilos en esa situación.

No puede reiniciar un objeto de hilo detenido; no lo intentes En su lugar, cree una nueva instancia del objeto si desea reiniciarlo después de que realmente se haya detenido y unido.

He jugado con diferentes herramientas para ayudar a limpiar el trabajo con hilos, procesamiento inactivo, etc.

make_idle es un decorador de funciones que le permite ejecutar una tarea en segundo plano de forma cooperativa. Este es un buen punto medio entre algo lo suficientemente corto como para ejecutarse una vez en el hilo de la interfaz de usuario y no afectar la capacidad de respuesta de la aplicación y hacer un hilo completo en sincronización especial. Dentro de la función decorada usas & Quot; yield & Quot; para devolver el procesamiento a la GUI para que pueda seguir respondiendo y la próxima vez que la UI esté inactiva, se reanudará en su función donde la dejó. Entonces, para comenzar, simplemente llame a idle_add a la función decorada.

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

Si necesita hacer un poco más de procesamiento, puede usar un administrador de contexto para bloquear el hilo de la interfaz de usuario cuando sea necesario para ayudar a que el código sea un poco más seguro

@contextlib.contextmanager
def gtk_critical_section():
    gtk.gdk.threads_enter()
    try:
        yield
    finally:
        gtk.gdk.threads_leave()

con eso puedes simplemente

with gtk_critical_section():
    ... processing ...

Todavía no he terminado con él, pero al combinar hacer cosas puramente inactivo y puramente en un hilo, tengo un decorador (no probado aún, así que no publicado) que puede decirle si la siguiente sección después del rendimiento es para ejecutarse en el tiempo de inactividad de la interfaz de usuario o en un hilo. Esto permitiría hacer una configuración en el subproceso de la interfaz de usuario, cambiar a un nuevo subproceso para hacer cosas de fondo y luego cambiar al tiempo de inactividad de la interfaz de usuario para hacer la limpieza, minimizando la necesidad de bloqueos.

No he visto en detalle su código. Pero veo dos soluciones a su problema:

No use hilos en absoluto. En su lugar, use un tiempo de espera, como este:

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()

Cuando use hilos, debe asegurarse de que su código GUI solo se llame desde un hilo al mismo tiempo protegiéndolo así:

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()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top