Programación Asíncrona en Windows Phone 7

Hola a todos!

Una parte muy importante de la experiencia de usuario de nuestra aplicación móvil es la fluidez y rapidez con la que esta responde al usuario, todos hemos usado aplicaciones que ante cierta acción se quedan congeladas durante un periodo de tiempo antes de volver a responder, lo primero que pensamos es que la aplicación se ha bloqueado y la solución por la que optamos suele ser reiniciarla, aunque esperemos y la aplicación vuelva a ser operativa nuestra impresión hacia la usabilidad de la misma suele ser negativa. Además de esto, en Windows Phone 7 uno de los requerimientos para que nuestro desarrollo supere la aceptación en el marketplace es que la interface de usuario (UI) nunca se “congele”, siempre responda al usuario.

Para llevar a cabo esto, podemos hacer uso de distintos mecanismos de programación asíncrona incluidos en el framework de .NET 4, en esta ocasión vamos a ver como usar la clase BackgroundWorker para obtener de forma sencilla ejecución asíncrona en nuestra aplicación.

Hilos en Windows Phone 7

Toda aplicación Silverlight para WP7 se compone por defecto de dos Hilos:

  • Hilo de Interface de usuario: Es el hilo principal de nuestra aplicación, se encarga de manejar la entrada de usuario, procesar los objetos creados en XAML y ejecutar el resto de nuestro código.
  • Hilo de Composición: Se encarga de manejar tareas que normalmente serían responsabilidad del Hilo de UI, mejorando el rendimiento de las aplicaciones. Por ejemplo este hilo se encarga de procesar, combinar texturas gráficas y enviarlas a la GPU del dispositivo para que las dibuje, también se encarga de manejar las animaciones creadas con Storyboards, enviándolas automáticamente a la GPU y liberando así al hilo principal. Toda transformación realizada con los siguientes elementos se manejará en el hilo de composición:

Sin embargo el hilo de composición tiene limitaciones que pueden hacer que nuestra transformación pase a ejecutarse en el Hilo de UI, con el claro detrimento de rendimiento que experimentaríamos como consecuencia, para evitarlo en la medida de lo posible debemos tener en cuenta estos puntos:

  • Si para la Opacity usamos una máscara de opacidad, se procesará en el hilo de UI.
  • Si queremos hacer Clip de un área no rectangular, se procesará en el hilo de UI.
  • Si establecemos un ScaleTransform mayor al 50% del tamaño, se procesará en el hilo de UI.

Es muy importante que tengamos estos simples puntos en mente e intentemos evitarlos en la medida de lo posible, cada milisegundo ganado en el Hilo de Composición nos aportará mayor fluidez en nuestra interface de usuario.

BackgroundWorker

La clase BackgroundWorker es el método más sencillo para ejecutar código en un nuevo hilo. Tiene incluida funcionalidad para cancelar el procesamiento asíncrono, notificar el progreso, ejecutar código en el nuevo hilo y notificar la finalización de la tarea. Se encuentra definida en el namespace System.ComponentModel.

Es muy sencillo trabajar con esta clase, básicamente, debemos crear un manejador para el evento DoWork, este manejador es el que se ejecutará en el nuevo hilo dedicado, disponemos del evento ProgressChanged, que podemos manejar y lanzar durante el progreso de nuestra operación con el método ReportProgress. Una vez que la ejecución ha terminado se lanza el evento RunWorkerCompleted que de nuevo se ejecuta en nuestro hilo principal, en el que podemos comprobar si el proceso termino con errores, si se cancelo o si termino correctamente.

Durante la ejecución de nuestro código en el evento DoWork (asíncrono) desde el hilo principal podemos llamar al método CancelAsync, haciendo esto, si comprobamos la propiedad CancellationPending desde nuestro código asíncrono, obtendremos su valor a true, lo que siginifica que el usuario ha solicitado la cancelación, simplemente deberemos establecer a True la propiedad Cancel de DoWorkEventArgs y salir del método lo antes posible, momento en el que se lanzará el evento RunWorkerCompleted y, comprobando la propiedad Cancelled de RunWorkerCompletedEventArgs obtendremos true y podremos responder de forma adecuada.

Vamos a ver esto pasos con código para que quede más claro, tengo un pequeño ejemplo, una aplicación que cada 1,5 segundos añade un item a un listbox indicando el progreso de la tarea, usando para todo ello la clase BackgroundWorker:

Primero tenemos que inicializar una nueva instancia, indicandole que soportamos notificar progreso y cancelación (de lo contrario, al intentar cancelar o informar del progreso recibiremos una excepción de tipo InvalidOperationException), también vamos a crear los manejadores de eventos para DoWork, ProgressChanged y RunWorkerCompleted:

BackgroundWorker worker = new BackgroundWorker();

worker.WorkerSupportsCancellation = true;
worker.WorkerReportsProgress = true;

worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);

 

A continuación incorporamos el código que deseemos ejecutar en el hilo asíncrono al evento DoWork (CUIDADO! este código no puede acceder directamente a la interface de usuario o recibiremos una excepción de Cross Threading inválido):

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    for (int i = 1; i < 10; i++)
    {
        Thread.Sleep(1500);
        worker.ReportProgress(i*10);
        if (worker.CancellationPending)
        {
            e.Cancel = true;
            return;
        }
    }
}

 

¿Y que pasa si queremos actualizar un control desde este código? Bueno, para esto WPF/Silverlight y por supuesto Windows Phone 7 ponen a nuestra disposición el Dispatcher, que básicamente nos sirve para indicar a un control que deseamos que ejecute código por nosotros, en el ejemplo anterior podriamos usar el Dispatcher de la ventana para que añada un Item a nuestro Listbox, en vez de usar el método ReportProgress:

this.Dispatcher.BeginInvoke((Action)delegate()
    {
        listCurrentOP.Items.Add("Elemento añadido con dispatcher.");
    });

 

Simplemente estamos indicanto el código que queremos ejecutar, y la ventana se encargará de ejecutarlo por nosotros, además, al usar BeginInvoke (en Windows Phone 7 solo está disponible este método) la ejecución continua automáticamente sin esperar a que se ejecute el código indicado al Dispatcher, por lo que continuamos nuestra filosofía de que nuestra aplicación siempre sea “usable”.

Cada vez que llamamos al método ReportProgress pasandole un valor numérico se lanza el evento ProgressChanged en el que, por ejemplo, podemos actualizar un control progressbar que muestre al usuario el progreso de la tarea, ten en cuenta que este evento SI se ejecuta en el hilo principal,  por lo que podrás acceder a los controles sin ningún problema de Cross Threading:

void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    listCurrentOP.Items.Add(string.Format("Progreso: {0}.", e.ProgressPercentage));
}

 

Por último, una vez que el evento DoWork a terminado, se lanza el evento RunWorkerCompleted, donde podremos controlar posibles errores, cancelaciones o que todo ha funcionado correctamente:

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    StartWorker.IsEnabled = true;
    if (e.Cancelled)
    {
        listCurrentOP.Items.Add("Cancelado por el usuario.");
        return;
    }
    if (e.Error != null)
    {
        listCurrentOP.Items.Add("Error.");
        return;
    }

    listCurrentOP.Items.Add("Completado.");
}

 

Y listo! ya tenemos ejecución asíncrona en nuestras operaciones más pesadas, con cancelación, notificación de progreso y notificación de terminación, además el uso de la clase BackgroundWorker no es nada complicado.

 Os dejo para que podáis descargar el proyecto usado en este ejemplo, en Visual Studio 2010 para Windows Phone 7.

Un saludo a todos, gracias por leerme y Happy Coding!

8 comentarios sobre “Programación Asíncrona en Windows Phone 7”

  1. Buen post, si señor…
    Una pregunta: Entiendo que wl manejo de los dos hilos (UI & composición) es automático, no?
    Supongo que nosotros sólo debemos crear nuevos hilos para otros procesos, como temas de asincronismo.
    A ver si en SL5 se incluye la TPL, vendría muy bien ahora que parece que estan apareciendo dispositivos con varios cores.

  2. Buenas Lluis!
    muchas gracias, me alegro que te haya gustado el post 🙂

    Efectivamente, el manejo de los dos hilos principales (UI & Composition) es totalmente automático, y está fuera de nuestro control, nosotros solo debemos preocuparnos de intentar darles todo el tiempo posible de proceso para tener una interface fluida, lanzando procesos largos o pesados en un hilo nuevo de manera asíncrona.

    Respecto a SL5.. dios te oiga que decía mi abuela, si le ponen TPL y lo implementan en XBox 360… sería un hombre feliz 😛

  3. Saludos, Josue.
    No se si estos post dedicados al WP7 estan motivados ademas porque tienes uno. O al menos me haria falta saber de alguien de Geeksboys que tenga uno.
    El asunto es que tengo uno desde hace un mes y no doy «pie con bola» en el tema de las apps. He instalado varias a traves del marketplace pero no me aparecen en el telf. Y he buscado
    por todas partes y nada. Incluyendo dentro de Games y de HTC Hub. Alguna idea ?

  4. Hola Alejandro!

    Si lo que instalas son juegos los puedes encontrar en el HUB de Xbox Live, si son aplicaciones las tienes listadas por orden alfabético en la lista de aplicaciones que aparece si desplazas la pantalla a la derecha (arriba a la derecha tienes una flecha que apunta a la derecha, si la pulsas hace el mismo efecto)allí encontrarás todas las aplicaciones, de lo contrarío podria tratarse de un fallo en el teléfono.

    Un saludo!

  5. Hola, tengo un problema, ojala alguien me pueda ayudar a corregirlo:

    Tengo una aplicacion que invoca 3 webservices, los 2 primeros los invoco en el metodo que inicializa todos los controles de la vista, pero el tercero no puedo invocarlo, ya que necesito hacerlo desde un metodo privado y no puedo ejecutarlo, alguien podria ayudarme con este problema?

    Gracias por su pronta respuesta.

  6. Saludos Josue, gracias por responder.
    El problema ya se arreglo. Me parece que todo estaba dado porque donde trabajo no hay gran cobertura para el marketplace (aunq salio una actualizacion que ahora si carga, pero antes ni sonharlo, solo a traves del Zune). Asi que instalaba cosas y nada de nada, incluso despues de 15 dias. Pero este sabado entre al marketplace (estaba en espacio abierto) y me salieron 3 updates, despues de eso se ven todas las apps. Por cierto, alguna noticia nueva de los updates programados para febrero (como copy-paste) ?
    Por cierto, gracias por responder.

Responder a jyeray Cancelar respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *