Archive for the ‘.net’ Category

Exportar con AODL

Domingo, 3 \03UTC septiembre , 2006

Hoy he acabado otra de esas funcionalidades para ProyectoRadio que tenía pendientes en mi ToDo desde casi el primer día, o bueno, desde el día que hice el exportador a Word. Tachán, aquí presento El exportador a OpenDocument (Text ). Eso sí, originalmente la idea era exportar a Open Office, pero por el medio se especificó el estándar OpenDocument.

Me gustan mucho este tipo de formatos, están totalmente especificados y estandarizados. Así, cualquiera puede coger esa especificación y escribir un programa que sea capaz de editarlos. OpenOffice implementa estos formatos como el formato predefinido para sus documentos. Como además soy usuario habitual de OO en mi trabajo y de forma personal, pues mejor aún.

Basándome en el párrafo anterior, muy hábilmente podría haberme bajado las especificaciones e implementado la funcionalidad necesaria para poder exportar desde ProyectoRadio hacia este formato. Pero aún más habilmente, google me ha ayudado a encontrar una librería (AODL) que lo hace por mí.

AODL es una librería para .NET que permite crear, abrir y editar documentos de texto (.odt) y de hoja de cálculo (.ods) de las especificaciones OpenDocument. En principio está escrita para .NET 1.1 pero yo la he enlazado desde .NET 2 y funciona sin problemas. Su licencia es LGPL y su funcionamiento bastante sencillo una vez se le coge el truquillo.

Hoy, en un par de horas, he escrito un exportador que es capaz de escribir documentos ODT con tablas e imágenes para exportar redes de radio. Aquí tenéis un ficherito de ejemplo. Este fichero es el resultado de exportar una red de prueba mediante el nuevo plugin que he escrito con AODL. Está recortado para que no ocupe en exceso. Me gusta porque está escrito con estilos y luego es fácil cambiar la apariencia del documento de golpe.

Lo que no he conseguido es introducir un salto de página. Supongo que será cosa de darle un par de vueltillas más. A ver si un día con algo más de tiempo escribo un pequeño tutorial de como trabajar con AODL.

Aquí tenéis una pequeña captura de pantalla de la herramienta de exportación con el exportador a ODT seleccionado.

OdtExporter Espero que os guste :).

Interfaces gráficas en entornos multihilo

Jueves, 31 \31UTC agosto , 2006

ProyectoRadio está implementado completamente sobre la plataforma .NET 2. No es que me guste más o menos que otras, pero como comenté en mi post sobre C, C# es un lenguaje que me parece muy potente y la plataforma permite desarrollar proyectos bastante complejos de una forma asequible.

La naturaleza del software requiere hacer tareas pesadas o largas. Por ejemplo las simulaciones de coberturas, la importación o la exportación de datos, la carga de mapas, etc son tareas que cuando se ejectuan tienen el control del programa durante un tiempo bastante importante.

La naturaleza de las aplicaciones .NET con interfaz gráfica de usuario Windows.Forms es básicamente la misma que la de la API de Windows nativa, solo que muy organizada y bonita. Las aplicaciones son del tipo “orientadas a eventos”. Esto quiere decir que existe un hilo de ejecución único asociado a la aplicación que está siempre dormido. Cuando la aplicación recibe un evento: Click en un botón o desplazamiento de una barra, el sistema le envía un mensaje que la función principal ejectua (todo esto de forma oculta claro) y llama a la función apropiada para manejar ese click.

Lo que el programador escribe son las funciones asociadas a los eventos. Cada control de usuario puede tener unos cuantos eventos. Por ejemplo un control de tipo Button tiene el evento Click. En C# se pueden asociar funciones a eventos de tal forma que si nosotros queremos gestionar el evento de Click solo hay que asociar una función al evento y cada vez que se pulse el botón se llamara a nuestra función. Bien.

Hasta aquí todo parece fácil. El problema veradero viene cuando asociado a ese handler o función asociada a un evento hay una operación muy lenta o costosa. Ejemplos un poco más arriba. Si no se hace nada y sin más se ejecuta la operación, el hilo de ejecución, que es único está entretenido operando y haciendo cuentas costosas y por tanto no retorna del evento, y por supuesto no puede atender más eventos del sistema, por mucho que estos lleguen. El resultado es bastante conocido, la interfaz de usuario (UI) se queda “como bloqueada” y si movemos la ventana fuera del escritorio y la traemos de nuevo se queda blanca. Normal, no se está gestionando el evento de repintado :).

Esto me ha traído bastantes problemillas. A primera vista la solución parece fácil. Usar un hilo de ejecución paralelo para ejecutar la tarea pesada. La idea es tan sencilla como demoníaca. Esto es fácil en C# y .NET. Hay una clase asociada a un hilo o thread de ejecución. Sin más que instanciar uno de estos objetos, pasarle una función de ejecución y lanzarla ya tenemos un hilo ejecutándose en paralelo al hilo principal. Bueno, pues bien, ahora nuestro hilo principal, después de lanzar el hilo pesado retorna del gestor del evento y la interfaz sigue respondiendo como es debido. Claro.

Ójala fuera todo así de fácil. Si estamos familiarizados con los sistemas paralelos y la programación concurrente rápidamente vamos a ver un problemilla: Hay que garantizar la coherencia de los datos. Esto quiere decir básicamente que todos los datos que necesite el hilo pesado ejecutándose en background no se modifiquen, o lo hagan de forma coherente, por el hilo principal de ejecución. Esto implica que cualquier pulsación de botón, desplazamiento, etc no modifique los datos importantes. Formas de garantizar esto existen, claro. Una de ellas es sin más haciendo una copia de los datos (shadow) y que estos sean los usados por el hilo de background. No creo que sea la ideal. Tenemos otras como usar mecanismos de interlocking. Ejemplos hay variados: Mutexes, CriticalRegions, Semaphores… lo que sea para bloquear al hilo principal de modificar los datos del hilo pesado. Hay que tener de nuevo cuidado de no bloquear la UI en uno de estos mecanismos.

Además de estos problemas evidentes y muy conocidos, la UI presenta otros problemillas cuando se usa en un entorno multihilo. Solo se puede acceder o modificar los valores de los controles, incluyendo su repintado, desde el hilo donde se crearon. Así, si se llama al constructor de un boton en el hilo principal, que será lo común, cualquier acceso que se haga desde el hilo secundario a este botón: Ver su estado, deshabilitarlo, etc… lanzará una excepción muy bonita y nos obligará a salir de la aplicación con toda probabilidad.

Me imagino que es una forma de proteger la implementación de los controles sin necesidad de complicados mecanismos de interbloqueo pero es una putada. Si te das cuenta que desde el hilo de la tarea pesada es necesario modificar o acceder a la UI, para dar un poquito de feedback o para actualizar una vista, pues cuidadín.

Alternativas a este problema hay muchas, pero principalmente dos: Una alternativa sencilla para problemas sencillos es usar un BackgroundWorker. Este tipo de objeto es una careta a un thread e implementa funciones útiles. Está pensado para el problema que yo planteo así que una vez arrancado el hilo principal retorna y la interfaz sigue funcionando. Cuando desde el hilo pesado hay que dar feedback se llama a una función del objeto BackgroundWorker (GiveFeedback()) y un evento se lanza pero en el hilo de ejecución que creó al BackgroundWorker. Problema resuelto, el feedback se puede dar en el hilo correcto sin más problemas. Lo mismo pasa para cancelar o para terminar el hilo.

El problema a esta primera solución aparece cuando los objetos que se gestionan en el hilo pesado son más complicados y el feedback no es tan sencillo y requiere modificar múltiples vistas. Otro problema es cuando los objetos modificados en el hilo secundario tienen sus propios eventos que pueden ser gestionados por parte de la UI. La solución a este problema puede ser bastante sencilla si se piensa desde un principio.

Allí donde la UI esté asociada a un evento de un objeto y que provoque un cambio o acceso a la UI es necesario comprobar el contexto de la llamada. Todos los controles, bueno realmente todos los objetos heredados de Component implementan una funcionalidad para evitar el problema de los hilos inapropiados. La propiedad InvokeRequired indica si es necesario hacer malabarimos para actualizar la UI. Retorna false en caso de que el hilo es el correcto y true en el caso que el hilo desde el que se llama sea otro diferente a la de creación. De nuevo los controles tienen una función, bueno una pequeña colección de ellas, que permite invocar la ejecución de una función en el hilo correcto. BeginInvoke() e Invoke() son las más importantes. Ambas funciones van a lanzar la ejecución de la función que le indiquemos como parámetro pero en el hilo correcto.

Bufff, esto es todo sobre UI y multithreading que no es poco. La verdad es que puede dar verdaderos dolores de cabeza sin no se diseña desde el principio con cuidado.