miércoles, 17 de junio de 2015

Threading: Actualizando Winforms

Introducción

Es muy posible que estando en proyecto de gráficos y al inicio de él, las prioridades serán que el gráfico se visualice correctamente en la ventana, con la información correcta, que se pueda manipular el objeto que se saca por pantalla, y dejando otras funcionalidades para la siguiente iteración.

Para ilustrar este post, imaginemos un proyecto software de tipo SIG (Sistema de Información Geográfica) donde hay muchas entidades geográficas a visualizar,
tantas, que se almacenan en una base de datos, y solo se pintan las entidades que corresponden con el área que se visualiza en pantalla.

Bien, en un principio...era la nada...luego el big bag..ok..al tema. Se visualizarían las entidades de una región en pantalla, pero luego el usuario, va moviendo esa región, o se aleja, esto generará que haya más entidades de mapa a visualizar, como no están en memoria, se piden a la base de datos,
la base de datos las retorna, y una vez retornadas se invalidará (Invalidate()) la ventana para que se repinte el mapa con la nueva información.

Threading

Entonces, llega el cliente o jefe..en fin un stakeholder del proyecto y dice:
"¿Y no se puede dibujar las entidades a la vez que las va leyendo de la base de datos?". Y en ese momento, empieza la cabeza a meterse en el código y ver que modificaciones habría que hacer, cual sería el nivel del impacto, y el resultado al cabo de un segundo es.."uff..no es inmediato..".

Solución


En el pequeño ejemplo que me he inventado, parece muy simple, pero cuando estas en un proyecto real, las cosas no son tan simples, pero hay que intentar reducir la complejidad al mínimo posible.

Aquí se ha intentado reducir la complejidad o ocultarla en la clase BackgroundPainter, que su responsabilidad sería la de leer de la base de datos, y pintar las entidades del mapa leídas, además de añadirlas al mapa. Aunque el leer de la base de datos estarías en la clase EntitiesDDBB y el dibujar en las clases hijas de MapEntity. Es decir que BackgroundPainter sería una clase controlador.


Se ha derivado de la clase BackgroundWorker, para de estar forma hacer la concurrencia más fácil, ya que cuando se termina de pintar todas, se lanzará
el evento RunWorkerCompleted, donde se invalidará la ventana y se repintará con la nueva información.


Se sobreescribe el método OnDoWork para que en este método se haga el trabajo de obtener las entidades de base de datos, añadir al mapa y dibujarlas.

En la aplicación, el botón "Get entities from DDBB", simula que el usuario a cambiado de región o alguna circunstancia que haga que se tenga que cargar nuevas entidades de base de datos. 
Se puede ver mientras se pinta las entidades, como se puede interactuar con el UI:


El código fuente se puede descargar aquí.

Conclusión


Habrá mucha soluciones, pero esta me ha parecido curiosa por el hecho de crear una clase que derive de BackgroundWorker y así, crear un worker en background donde ocultar
la complejidad de la aplicación y no llevar esta complejidad en MainForm.cs o en otra clase existente. He incluso para complicarlos más, esta clase BackgroundPainter podría recuperar
la textura del terreno en background con otro thread.