Cross Threading en MVVM y Windows Phone 8 (y también 7)

 

Os cuento una rápida que seguro os va a solucionar un montón de quebraderos de cabeza y que me ha traído loco un rato hasta que me he dado cuenta del pufo.

Todo viene cuando estás usando el patrón MVVM y varías algún dato que se está mostrando en algún componente visual. Todos los tutoriales te dicen que heredes de ObservableCollection y que implementes el método NotifyPropertyChanged(). Y te ponen el típico ejemplo:

public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
    var handler = PropertyChanged;
    if (null != handler)
    {
      handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Hasta aquí nada extraño. Cuando una propiedad cambia su valor, quien quiera que esté observando tu colección se enterará del mismo y actuará en consecuencia. Eso si alguien está observando tu colección, que no siempre ocurre, sobre todo si hay algún converter de por medio (ya hablaré largo y tendido sobre esto).

Pero el problema viene cuando ese cambio se produce fuera del hilo principal de la aplicación, como suele pasar a poco que estés haciendo algo medianamente serio. No veo yo bloqueando tu aplicación mientras un servicio web o lo que sea se baja lo que se tenga que bajar, aparte de que en Windows Phone la mayoría de llamadas a todo eso son asíncronas.

Si haces un cambio en un hilo que no sea el de la UI, en Windows Phone 7.5 no pasa nada. Simplemente se ejecuta el código y en general tu colección cambia de valor y a veces, sólo a veces, la visualización cambiará. O no.

En Windows Phone 8 ocurre otra cosa no muy diferente. Recibes dos first chance de dos excepciones diferentes, el IDE no te captura nada a no ser que cambies la configuración de captura de excepciones y el hilo en el que se esté ejecutando el código se muere silenciosamente. Y por supuesto no se cambia nada en la parte visual de tu aplicación.

Vale. Tonto si no pones un bloque try/catch en cada hilo. Pero aquí estamos hablando de otro combate.

¿Cómo puñetas puedo actualizar eso de forma automática? Es decir, ¿tengo que hacer un BeginInvoke() cada vez que asigne algo? La respuesta corta es que sí. La larga es lo siguiente.

Primero tienes que crearte una propiedad nueva en la clase App que vas a llamar UiDispatcher (o como te salga de los OO):

internal static Dispatcher UiDispatcher=Current.RootVisual.Dispatcher;

Y luego cambia el código de arriba por el siguiente:

        public event PropertyChangedEventHandler PropertyChanged;
        private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var handler = PropertyChanged;
            if (null != handler)
            {
                if (App.UiDispatcher.CheckAccess())
                    handler(this, new PropertyChangedEventArgs(propertyName));
                else
                    App.UiDispatcher.BeginInvoke(() => handler(this, new PropertyChangedEventArgs(propertyName)));
            }
        }  

Ahora, si os fijáis, si el cambio de vuestra propiedad se realiza dentro del hilo principal, se llamará directamente, y si lo hace desde otro lo hará dentro del Dispatcher de la UI.

¿Por qué una propiedad y no llamar siempre a Current.RootVisual.Dispatcher? Pues porque podría interesarnos tener diferentes dispatchers para otras cosas, pero esa es otra guerra que hay que combatir en otro momento.

Un comentario en “Cross Threading en MVVM y Windows Phone 8 (y también 7)”

Deja un comentario

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