Threads com PyGTK
Já falei um pouco de thread em python; agora vamos falar de threads com pygtk.
Como uma introdução, analise o código abaixo. Veja se lhe parece correto.
#! /usr/bin/env python
# -.- coding: utf-8 -.-
import gtk
import pygtk
continua=None
def iniciar(botao, texto):
global continua
continua=True
x=0
while continua:
texto.set_text(str(x))
x+=1
def parar(botao):
global continua
continua=False
# criando os componentes
win=gtk.Window()
box=gtk.VBox()
texto=gtk.Entry()
btinicia=gtk.Button("iniciar" )
btpara=gtk.Button("interromper" )
# ativando os eventos
btinicia.connect("clicked", iniciar, texto)
btpara.connect("clicked", parar)
win.connect("destroy",gtk.main_quit)
# encaixotando tudo
box.pack_start(texto)
box.pack_start(btinicia)
box.pack_start(btpara)
win.add(box)
# exibindo tudo
win.show_all()
# iniciando o loop principal do GTK
gtk.main()
O que deveria acontecer ao executar esse código? Uma janela com uma caixa de texto, um botão “iniciar” e um botão “interromper”.
Clicando no “iniciar”, um loop inicia e começa a mudar o conteúdo da caixa de texto para “1″, “2″ e assim por diante até que se clique em “interromper”.
Se quiser executar o código, fique à vontade. Mas vou adiantar aqui que isso não vai dar certo. Ao clicar em “iniciar”, o controle do programa passa para o loop sem fim, e você nem vê a caixa de texto sendo atualizada e nem consegue mais clicar no botão “interromper”. Simplesmente seu programa pára de responder.
Para que o programa funcione como esperado, é necessário criar uma thread onde será executada a função iniciar. A função então vai rodar em paralelo, e você assume de novo o controle do programa.
************
Atualização!
Conforme o comentário do thotypous, é possível fazer esse código funcionar mesmo sem threads.
Basta usar while gtk.events_pending():gtk.main_iteration() dentro do loop:
while continua:
while gtk.events_pending():gtk.main_iteration()
texto.set_text(str(x))
x+=1
Como o objetivo deste artigo é mostrar o uso de threads com pygtk, vamos tocar o barco.
************
Para utilizar threads em pygtk, além do que já aprendeu sobre threads você vai ter que lembrar de duas coisinhas:
- iniciar threads do gtk com gtk.gdk.threads_init
- usar gobject.idle_add para permitir que códigos externos ao gtk.main_loop possam modificar sua interface em pygtk.
( referência: Using threads in PyGTK )
A função gobject.idle_add( callback, [argumentos para callback] ) adiciona uma função (especificada por callback) a ser chamada sempre que não houver eventos de prioridade mais alta para o main loop. Os argumentos para a função callback são passados como argumentos adicionais de gobject.idle_add(). (livre tradução da definição em gobject.idle_add )
Vamos ver então como fazer o código apresentado lá em cima funcionar direito:
from threading import Thread
import time
import gtk, gobject
gtk.gdk.threads_init()
a_thread=None
def iniciar(botao, texto):
global a_thread
#iniciando a thread
a_thread=Thread(target=nosso_loop, args=(texto,botao))
a_thread.morta=False
a_thread.start()
def nosso_loop(texto, botao):
# desabilitando o botao "iniciar"
# para que o usuario nao possa
# iniciar novas threads enquanto
# esta continuar
botao.set_sensitive(0)
z=0
while not a_thread.morta:
z+=1
# chamando a funcao atualiza_texto
# com o idle_add
gobject.idle_add(atualiza_texto,texto, z)
time.sleep(0.00001)
# o loop terminou
# o botao vai poder agora
# ser clicado de no:vo
botao.set_sensitive(1)
def atualiza_texto(texto, valor):
texto.set_text(str(valor))
def interromper(botao=None):
a_thread.morta=True
def sair(win):
interromper()
gtk.main_quit()
#criando os componentes
win=gtk.Window()
box=gtk.VBox()
texto=gtk.Entry()
botao=gtk.Button("iniciar" )
btmata=gtk.Button("interromper" )
#ativando os eventos
botao.connect("clicked", iniciar, texto)
btmata.connect("clicked", interromper)
win.connect("destroy",sair)
#encaixotando tudo
box.pack_start(texto)
box.pack_start(botao)
box.pack_start(btmata)
win.add(box)
#exibindo tudo
win.show_all()
#iniciando o loop principal do GTK
gtk.main()
Incluimos a thread neste código. A função “iniciar()” agora simplesmente inicia a thread. O loop infinito foi transferido para a função “nosso_loop()”, que será executada pela thread.
A atualização da caixa de texto foi transferida para a função “atualiza_texto()”, que é executada com “gobject.idle_Add()”.
Pode executar este código. Vai funcionar.
17 / Julho / 2008 às 12:49 am
Poderia ser feito sem threads usando gtk.main_iteration(False) dentro do loop.
Para loops mais complicados, que usem I/O por exemplo, esta dica é interessante: http://faq.pygtk.org/index.py?req=show&file=faq20.009.htp
26 / Janeiro / 2009 às 3:58 pm
Estou tentando fazer as threads funcionarem com o pygtk, mas o python sempre trava. Copiei e colei seu código e mesmo assim trava.
Estou tentando rodar com o python 2.5, pygtk 2.12.1 e gtk 2.0 sobre o windows XP.