Uno de los principales problemas al momento de crear aplicaciones de escritorio es el manejo del flujo de ejecución. Este problema se complica considerablemente al tener que ejecutar funciones bloqueantes. Twisted, a pesar de estar diseñado para networking, tiene una serie de herramientas para el maenjo ascincrónico del flujo facilmente adaptables a los Toolkits gráficos mas populares.
Usando Twisted para hacer aplicaciones de escritorio no bloqueantes
1. Usando Twisted para hacer aplicaciones
de escritorio no bloqueantes
Martín Volpe
@martinvol
PyCon Argentina
Bernal, Buenos Aires, Argentina, Noviembre 2012
Versión online: http://volteck.net/development/twisted-escritorio-charla.rar
2. Aclaraciones:
• Para el objetivo de esta charla: Twisted = Asincrónico
• Twisted no es la única forma
• Los ejemplos van a ser triviales
• Los usuarios se desesperan fácil
• ¿Alguien programó en Assembler? (no se asusten!)
4. Objetivos de la charla:
• Entender porque las aplicaciones se bloquean
• Mostrar como Twisted se acopla a este problema
• Entender las diferentes herramientas que tiene Twisted
para solucionarlos
• Entender que Twisted sólo parece magia, pero no lo es,
¡Es Python!
5. ¿Por qué pasa esto?:
¡Es culpa del reactor! ok..., para, ¿Qué es un reactor?
Un loop.
# esto!
while True:
stuff = wait_for_stuff(timeout)
if stuff:
stuff.execute()
run_timed_events()
#si esto tarda en llamarse, la interfaz se bloquea
refresh_GUI() #esta función de por si, tambien bloquea
• El reactor de Gtk, Qt, Wx y TKinter se pueden resumir a algo
parecido a esto.
• Los que hayan programado con PyGame seguramente
reconozcan este snippet
6. ¿Por qué pasa esto?:
Esperar eventos
Bucle del reactor
Ejecutar eventos
$ python examples/1.py
7. ¿Por qué pasa esto?
¡¡Es culpa del reactor!! ok..., pero, ¿Que es un reactor?
Bucle del reactor
Toolkit Callback
Tu código
$ python examples/2.py
8. Twisted:
• Nada mas ni nada menos que un FrameWork escrito en
Python
• Incluye un reactor extendible
• Originalmente creado para NetWorking, feature que
podemos seguir utilizando para aplicaciones gráficas
• Empezó a escribirse en 2002: es maduro y estable;
• Por esto mismo: no respeta PEP8
• Es MUY extenso
• Licenciado bajo MIT License
http://twistedmatrix.com
9. Dos conceptos
• The Hollywood principle: No nos llames, nosotros te
llamamos (no tan crítico en aplicaciones de escritorio)
• Multitasking cooperativo: todos compartimos el reactor,
nos comprometemos a liberarlo lo mas rápido posible
10. Ejemplos típicos:
• Buscar un recurso de red: urllib, sockets, etc
• Bucles y algoritmos matemáticos
• Llamadas a base de datos
• Interactuar con hardware
• print() (!!!)
• tus funciones
Estos ejemplos pueden no bloquear en todas las oportunidades,
hasta que bloquean.
Casi todo se puede implementar como función no bloqueante, lo
que no implica que sea la manera mas difundida (u obvia)
$ python examples/slow_server.py &
$ python examples/3.py
11. Una solución: Threads
Puede servir:
• Poco overhead
• Comparte el namespace
Pero:
• Muy poco control sobre lo que se está ejecutando (no se
pueden detener!)
• Interactuar con el GIL: http://blip.tv/carlfk/mindblowing-python-gil-2243379
Un ejemplo:
http://python.org.ar/pyar/Recetario/Gui/Gtk/MultiThread
12. Otra solución: Multiprocessing
Puede servir:
• Se puede detener la ejecución
• Utiliza múltiples cores (si el procesador es multi-core
puede correr rápido)
Pero:
• Overhead, abre otro intérprete de Python completamente
independiente
• Por eso mismo: no comparte namespace (hay que usar
Queues o Pipes)
http://python.org.ar/pyar/Recetario/MultiprocessingYThreading
13. Twisted: multitarea colaborativa
En el 99% de los casos esas soluciones clásicas son
aplicables, pero eso no quiere decir que sean las mas
adecuadas
Tarea 1
Tarea 1 Tarea 2 Tarea 3
Tarea 2
Tarea 3
Tiempo
14. Deferred:
Es un objeto que nos ayuda a controlar flujos de ejecución
asincrónicos
Fuente de datos
Deferred
resultado error
C
E
a
r
l
r
l
b
b
a
a
c
c
k
k
s
s
15. Deferred:
Es un objeto que nos ayuda a controlar flujos de ejecución
asincrónicos
Loop Callbacks
otros deferreds pueden
ejecutarse
17. Deferred:
class X:
Sincrónico:
def get_data(self):
# lanzo algo que eventualmente
# llame a data_ready
self.d = Deferred()
return self.d
def data_ready(self, data):
self.d.callback(data)
service = X()
service.get_data().addCallbacks(callbacks)
18. Deferred:
try:
guardar_bloqueante(unDato)
except Exception, e:
print "Algo no anduvo."
d = guardar_no_bloqueante(unDato)
# retorna enseguida
def handle(error):
print "Algo no anduvo."
d.addErrback(handle)
19. TIP: Es importante encapsular el código
Respetar el paradigma orientado a objetos nos ahorra
dolores de cabeza:
import guitoolkit
class Gui:
def on_button_click(self, widget): ...
def hide_entry1(self): ...
class App():
def __init__(self, gui): ...
def guardar(self): ...
def conseguir_datos(self): ...
if __name__ == '__main__':
gui_inst = Gui()
App(gui_inst)
guitoolkit.run()
20. Twisted entra en acción:
• Twisted es un framework diseñado para el desarrollo de
aplicaciones web; es basado en eventos
• Incluye su propio reactor
• Este reactor puede ser acoplado con Gtk, Tkinter, PyUI,
wxPython
• El reactor se puede acoplar a otros frameworks, como
PyGame.
• Y Qt? Por problemas de licencias no está incluido con el
código de twisted, pero se puede descargar por separado
http://twistedmatrix.com/trac/wiki/QTReactor
Documentación sobre los diferentes reactors:
http://twistedmatrix.com/documents/current/core/howto/choos
ing-reactor.html
21. Integrando con el toolkit gráfico:
from twisted.internet import gtk2reactor
# varía dependiendo el toolkit a integrar
gtk2reactor.install()
# todo tu código
# ...
from twisted.internet import reactor
# arrancamos el reactor de twisted
reactor.run()
$ python examples/twisted/1.py
• ¿Notaron que el código de gtk no cambio y no se rompió nada?
• Tambien hay que notar que sigue fallando en el mismo lugar,
acordate: ¡Twisted no hace magia!
22. Herramientas que incluye Twisted:
Estandariza muchas acciones comunes:
• reactor.callLater(segundos, funcion, *args, **kw), Llama a
funcion despues de pasados tantos segundos
• reactor.callWhenRunning(funcion, *args, **kw), Llama a funcion
cuando el reactor empieza a ejecutarse
• deferToThread(funcion, *args, **kwargs), Ejecuta funcion en otro
thread y retorna un objeto Deferred
• @inlineCallbacks, transforma funciones comunes en funciones
asincrónicas (para retornar hay que usar returnValue)
• Deferred.cancel(), cancelar una tarea en curso dentro de un
deferred
• deferLater(reactor, segundos, funcion), ejecuta funcion pasados
tantos segundos, y retorna un objeto Deferred
Y las API's de los diferentes toolkits siguen funcionando, Twisted
actua como un plugin.
23. Twisted entra en acción:
Reimplementemos los ejemplos usando lo que ya sabemos:
$ python examples/twisted/2.1.py
$ python examples/twisted/2.2.py # detenible
$ python examples/twisted/3.1.py
$ python examples/twisted/3.2.py # detenible
24. Caso práctico: Donnees
• Tareas asincrónicas
• Servidor Web
• Base de datos
• Gráficos en tiempo real
• Threads implementados con Twisted
• Envío de e-mail y generación de reportes (podría haber usado
twisted.mail.smtp.sendmail)
• Tomar datos del puerto serie
Repo: https://github.com/maritnvol/Donnees-Acquisition-Data-software
$ git clone git@github.com:maritnvol/Donnees-Acquisition-
Data-software.git
$ cd Donnees-Acquisition-Data-software/Donnees
$ python tilapia.py