En el TP de implementación de Adm. de Recursos desarrollamos con Nacho y César un cliente de mensajería instantánea muy simple. Está hecho en C# y para la GUI usamos Gtk#, todo corriendo en Mono. Por cierto, ésta es la página del proyecto por si quieren investigar el código y hacer algunas pruebas.
Pero el motivo del post no es este trabajo en sí, sino la solución a un problema que se había presentado. Utilizamos Remoting para comunicar los clientes con el servidor. Remoting es una tecnología para comunicar procesos, en la que podemos instanciar, por ejemplo, objetos remotos y llamar métodos del mismo, ejecutándose éstos en el servidor. Lo veo como algo muy parecido a RPC. Tampoco es el objetivo del post hablar de Remoting, sino simplemente decir que cuando ejecutamos un método remoto, se abre un thread nuevo para llevar a cabo la ejecución del mismo en el servidor.
En nuestro diseño (que quizá no sea el mejor, pero no importa), el servidor mantenía actualizada la lista de contactos de todos los clientes. Así, cuando se conectaba alguien, el servidor ejecutaba un método remoto de un objeto propio de los clientes, que se encargaba de actualizar el TreeView con el nuevo contacto recién conectado. Lo mismo pasaba cuando se recibía un mensaje nuevo enviado por un cliente a otro.
Gtk.Application.Invoke()
Notábamos que cuando se disparaban estos métodos remotos en el cliente, el cual actualizaba la interfaz gráfica, a veces se colgaba. No entendíamos por qué hasta que leímos un poco más sobre el funcionamiento interno de Gtk+. Algunos insultos recibió el toolkit antes de eso 🙂 El artículo interesante y que nos ayudó es éste. Resulta que Gtk+ es thread-aware, pero no thread-safe. O sea que, por lo que entiendo, en nuestra aplicación solamente el thread que ejecutó Application.Run puede acceder a los elementos de Gtk. Entonces nuestro problema era que ese thread (el que manejaba la ejecución del método remoto, distinto al thread principal) intentaba actualizar un TreeView directamente.
Entonces, lo que debemos hacer en el código de nuestro thread es encolar un evento para que sea ejecutado por el thread principal:
Gtk.Application.Invoke (delegate {
label.Text = "Listo";
});
Con ese delegate estoy declarando un método anónimo. La alternativa es crear un método aparte y pasarle a Invoke el nombre del método.
Idle Handlers
Todavía no me surgió la necesidad de usar esto, pero esta muy interesante, para tener en cuenta. Si necesitamos realizar cierta actividad cuando nuestro programa no está haciendo nada, podemos usar el siguiente código:
void Start ()
{
GLib.Idle.Add (new IdleHandler (OnIdleCreateThumbnail));
}
bool OnIdleCreateThumbnail ()
{
Image img = GetNextImage ();
// If no more images remain, stop the idle handler.
if (img == null)
return false;
CreateThumbnail (img, img.ToString () + ".thumbnail");
// There are more images, invoke this routine again on the next Idle moment.
return true;
}
Esto está bueno, porque nos olvidamos de la complejidad de usar threads para éste proposito en particular. El funcionamiento parece ser bastante entendible al mirar el código de ejemplo de arriba.