Para ciertos sistemas, es necesario realizar trabajos que toman un tiempo considerable, como por ejemplo la lectura de archivos grandes, la generación de archivos, la comunicación con sistemas en la web.
Para estos casos, es necesario que la aplicación permanezca activa e informe al usuario que se está realizando el trabajo, ya que de otra manera el usuario puede confundir la demora con una falla.
Una forma que he descubierto funciona bastante bien, es cada vez que se generan estos procesos, levantar un pequeño dialogo, con una barra de progreso infinita, que lo único que hace es dar esa información al usuario de que se está trabajando.
Veremos como implementar esta solución en C# 3.5 , utilizando hilos de ejecución paralela. Haremos esto de una manera sencilla y directa, más adelante ingresaremos en optimizaciones de diseño y detalles de implementación.
Si la aplicación está realizando un trabajo fuerte, es posible que ocupe toda o casi toda la capacidad del procesador en realizar la tarea, o quizás ocupe demasiado acceso a disco, o simplemente necesite esperar la información desde una fuente externa que se demora en entregarla.
En un escenario con estas características, es muy posible que el hilo de ejecución (el hilo que maneja la interfaz gráfica y toda la aplicación) se paralice en espera, y el sistema permanezca inactivo durante el proceso, y el usuario piense que se ha “pegado” o “colgado” la aplicación.
Si nos detenemos un poco a definir lo que necesitamos, es simplemente un dialogo que se ejecute en el hilo principal, pero que sea capaz de lanzar una tarea (cualquiera) en un hilo secundario, que presente una barra de progreso y que indique con un pequeño texto el proceso que se está realizando.
Para este caso vamos a usar una barra de progreso infinita (marquee) para que no nos tengamos que preocupar de actualizar el progreso (y de paso mantener la API simple) y el texto por ahora será fijo.
Como primer paso, entonces, creamos lo que va a ser nuestra interfaz gráfica. A mi me gusta comenzar por diagramar las ventanas ya que eso me permite tener los objetivos claros al principio, por lo menos para obtenter la funcionalidad básica.
Este es solo un primer prototipo, luego, en otros posts, mejoraremos el diseño (diseño de software y componentes) y tomaremos algunas decisiones de refactoring si es necesario.

Diseño inicial del diálogo
Luego que hemos hecho el trabajo de dibujar el diálogo, queda dibujar el formulario que va a llamar a nuestro dialogo.
Me imagino que vamos a necesitar un DataGridView para mostrar la algun tipo de data, y un botón de proceso. Luego inventaremos algo de data para probar el proceso.

Diseño Inicial de formulario
Muy bien, entonces, tenemos un formulario y un diálogo para iniciar la programación.
Para el primer requerimiento vamos a necesitar crear el diálogo y traspasarle la responsabilidad de realizar el trabajo requerido.
Como esto es un prototipo, iniciemos con algo sencillo, en el diálogo mismo, crearemos un delegado. Los delegados pueden entenderlos como “punteros a funciones” si vienen del mundo de C, y básicamente me permiten hacer esta funcionalidad que necesito, traspasar la responsabilidad de ejetutar un método a otra secuencia de control.
Los delegados necesitan una firma, es decir se declara que parametros recibe y que tipo de dato retorna, pero luego se puede especificar cualquier método que cumpla con estos parametros y tipos de retorno para que sea ejecutado. Todos los eventos en .net funcionan mediante delegados.
También vamos a necesitar exponer algunas propiedades del diálogo, como el texto a mostrar. De hecho, el delegado lo podemos exponer también como una propiedad.
Nuestro código del diálogo entonces estaría quedando algo similar a:
using System;
using System.Windows.Forms;
namespace DialogoProceso
{
public partial class dlgDialogoProceso : Form
{
// Declaración del delegado (declara la firma y el nombre que tendrá)
public delegate void trabajo();
// Delegado que usaremos para realizar finalmente el trabajo, entregado por parámetro al Constructor
public trabajo Delegado
{
get;
set;
}
/// <summary> Mensaje que se muestra en la pantalla de progreso</summary>
public String Mensaje
{
get { return lblTextoProceso.Text; }
set { lblTextoProceso.Text = value; }
}
/// <summary>Crea una nueva instancia del dialogo de proceso.</summary>
/// <param name="delegado">Trabajo a realizar por el diálogo, será lanzado en un nuevo hilo.</param>
public dlgDialogoProceso(trabajo delegado)
{
InitializeComponent();
Delegado = delegado;
}
private void On_Shown(object sender, EventArgs e)
{
// Al mostrar el formulario (al ejecutarse el metodo Show()
// hacemos el trabajo mediante el delegado
Delegado();
}
}
}
He tomado la decisión de mantener el delegado lo más simple posible: sin retorno y sin parámetros.
Para obtener data, yo creo que vamos a hacer algo entretenido y simple, vamos a obtener las noticias de Lifehacker en rss (http://lifehacker.com/tag/top/index.xml) en el DataGridView.
El método de trabajo lo vamos a colocar en el formulario principal para continuar con nuestra filosofía de prototipo. No te espantes, esto no es algo final ni está pensado para que lo uses directamente en código de producción, solo estamos desarrollando una funcionalidad principal desviándonos lo menos posible del objetivo, un prototipo.
Veamos como queda el código del formulario, con la inclusión de el evento del botón y el trabajo a realizar:
using System;
using System.Data;
using System.Windows.Forms;
namespace DialogoProceso
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void obtieneEnlaces()
{
var ds = new DataSet();
ds.ReadXml("http://lifehacker.com/tag/top/index.xml", XmlReadMode.Auto);
// El DataGridView se llama dgLinks
dgLinks.DataSource = ds.Tables["item"];
}
private void button1_Click(object sender, EventArgs e)
{
var proceso = new dlgDialogoProceso(obtieneEnlaces);
proceso.Mensaje = "Obteniendo noticias de Lifehacker";
proceso.Show();
}
}
}
Bastante simple, pero si ejecutas en este punto, te darás cuenta de que el problema que estamos tratando de resolver todavía existe, la interfaz todavía está bloqueada.
Esto solo lo solucionaremos incorporando los hilos de ejecución al sistema, y lo haremos mediante el muy útil objeto BackgroundWorker que nos encapsula el trabajo en hilos separados.
Todo el trabajo lo hacemos en el diálogo, reemplazamos el metodo On_Shown y agregamos un nuevo método que nos configurará un BackgroundWorker:
private void On_Shown(object sender, EventArgs e)
{
CreaTrabajador(Delegado).RunWorkerAsync();
}
private BackgroundWorker CreaTrabajador(trabajo delegado)
{
var trabajador = new BackgroundWorker();
// Definimos el trabajo a realizar por el BackgroundWorker
trabajador.DoWork += delegate {
delegado();
};
// Definimos que debe hacer al concluir el trabajo
trabajador.RunWorkerCompleted += delegate {
DialogResult = DialogResult.OK;
Close();
};
// Devolvemos el trabajador configurado
return trabajador;
}
Y ya tenemos listo nuestro prototipo.

Formulario con datos
Pueden obtener el código del prototipo aquí. Van a requerir visual studio 2008 para poder compilar. No viene compilado para ahorrar espacio.
Pero quedan muchas cosas que hacer. Esto es muy poco portable, no tenemos manejo de excepciones, y hay algo que me molesta sobre tener que lanzar un diálogo… la idea es que pueda seguir trabajando mientras se procesa la información.
En el siguiente post vamos a crear una interfaz (IPararlelizable) para mejorar la generalización de esta solución y veremos como manejar excepciones que se generan en hilos separados. En un tercer post, veremos como convertir esto en un control de usuario completamente reusable y finalmente tendremos algo listo para producción.
Estén atentos para la próxima semana.
||dELm||
Muchas gracias por tu aportación.
Estoy deseando que publiques ya la segunda y tercera parte de esta entrada.
Muchas gracias y un saludo.
comentario por koldo — Abril 17, 2009 @ 9:31 am
Para nada, muchas gracias por dejar un comentario.
Me diste el empujón que necesitaba para publicar el nuevo post.
Eso si, lamentablemente no va a haber un tercero.
comentario por David Lay — Abril 25, 2009 @ 3:12 am