Ejecución de operaciones en 2 plano


Como ya comenté en el post anterior algunas operaciones que puedan llevar bastante tiempo para completarse se realizarán en 2 plano ( en un hilo a parte ), en todos los ejemplos que he visto de WPF para hacer esto se utiliza la clase Dispatcher, asi que lo voy a implementar de la misma forma, aunque utilizando el BackgroundWorker de Windows Forms se podría conseguir lo mismo.

La ejecución de este tipo de operaciones  va a tener 2 métodos, uno en el que se realizará la operación en si y que se ejecutará en un hilo propio, y otro en el que se actualizará la vista ( en los casos que haga falta ) y que se ejecutará en hilo de la UI.

A modo de ejemplo voy a explicar ( o a intentarlo más bien ) como funcionará la operación de Refresh en la vista de Divisas ( que realiza una petición a un servicio de WCF para obtener los datos de la divisa que coincida con el código de divisa que se haya teclado en pantalla ).

La clase ViewModel tiene un método Refresh que se ejecuta cuando el usuario hace click en el botón de Refresh de la barra de herramientas o en la opción equivalente del menú, este método se encarga de poner en la cola del ThreadPool, con el método QueueUserWorkItem, la llamada al método OnRefresh que será el encargado de realizar la llamada al servicio WCF para obtener los datos, si la llamada a ThreadPool.QueueUserWorkItem no acaba correctamente la vista quedará en estado de no válida

   1: if (!ThreadPool.QueueUserWorkItem(new WaitCallback(delegate
   2:     {
   3:         this.OnRefresh(new ServiceResponse<TDataModel>());
   4:     })))
   5: {
   6:     this.ViewModelState = ViewModelStateType.Invalid;
   7: }

Los métodos de los servicios de WCF devolverán instancias de la clase ServiceResponse<T>, esta clase es así de sencilla ( por el momento, seguramente acabe llevando alguna cosa más jeje )

   1: [Serializable]
   2: public class ServiceResponse<T>
   3: {
   4:     #region · Fields ·
   5: 
   6:     private bool    succeed;
   7:     private string  message;
   8:     private T       dataModel;
   9:
  10:     #endregion
  11: 
  12:     #region · Properties ·
  13: 
  14:     public bool Succeed
  15:     {
  16:         get { return succeed; }
  17:         set { succeed = value; }
  18:     }
  19: 
  20:     public string Message
  21:     {
  22:         get { return message; }
  23:         set { message = value; }
  24:     }
  25: 
  26:     public T DataModel
  27:     {
  28:         get { return dataModel; }
  29:         set { dataModel = value; }
  30:     }
  31: 
  32:     #endregion
  33: 
  34:     #region · Constructors ·
  35: 
  36:     public ServiceResponse()
  37:     {
  38:     }
  39: 
  40:     #endregion
  41: }

La clase tiene solo 3 propiedades, una para indicar si la operación terminó correctamente, otra para el mensaje de error en el caso de que algo vaya mal y otra para los datos devueltos por el servicio de WCF.

La implementación por defecto del método OnRefresh en la clase ViewModel está así

   1: protected virtual void OnRefresh(ServiceResponse<TDataModel> state)
   2: {
   3:     this.Dispatcher.BeginInvoke(
   4:         DispatcherPriority.ApplicationIdle,
   5:         new ThreadStart(delegate
   6:             {
   7:                 this.OnRefreshComplete(state);
   8:             }));
   9: }

La propiedad Dispatcher devuelve la instancia del Disparcher que estaba “activa” en el momento de crearse la instancia de la clase VieWModel y nos servirá para realizar la llamada al método OnRefreshComplete desde el hilo de la UI

   1: #region · Constructors ·
   2: 
   3: /// <summary>
   4: /// Initializes a new instance of the <see cref="ViewModel&lt;TDataModel&gt;"/> class.
   5: /// </summary>
   6: protected ViewModel()
   7: {
   8:     this.dispatcher         = Dispatcher.CurrentDispatcher;
   9:     this.dataModel          = new TDataModel();
  10:     this.viewModelState     = ViewModelStateType.None;
  11: 
  12:     this.InitializeMetaData();
  13:     this.InitializeCommands();
  14:     this.RegisterCommands();
  15: 
  16:     this.ViewModelState     = ViewModelStateType.Default;
  17: }
  18: 
  19: #endregion

El método OnRefresh está sobreescrito en la implementación de la clase ViewModel para la vista de Divisas, DivisasViewModel

   1: protected override void OnRefresh(ServiceResponse<Divisa> state)
   2: {
   3:     state = (new ServicioConfiguracionFacadeClient()).Fetch(this.DataModel.IdDivisa);
   4: 
   5:     base.OnRefresh(state);
   6: }

Sencillamente se encarga de llamar al servicio de WCF para intentar obtener los datos y ejecuta la implementación de la clase base.

El método OnRefreshComplete de ViewModel se encargará de actualizar los datos en pantalla o mostrar un error en el caso de que algo no haya ido bien ( lo cual aún no he decido como lo voy a hacer🙂 ), en cuyo caso además se creará una nueva instancia del modelo y se notificarán los cambios a la vista de cara a dejar todos los datos en pantalla en blanco ( o con sus valores por defecto ).

   1: protected void OnRefreshComplete(ServiceResponse<TDataModel> state)
   2: {
   3:     this.VerifyCalledOnUIThread();
   4: 
   5:     if (state.Succeed)
   6:     {
   7:         this.DataFetched = state.Succeed;
   8: 
   9:         if (state.Succeed)
  10:         {
  11:             this.DataModel = state.DataModel;
  12:         }
  13:         else
  14:         {
  15:             this.DataModel = new TDataModel();
  16:         }
  17: 
  18:         this.NotifyAllPropertiesChanged();
  19:     }
  20:     else
  21:     {
  22:  TODO: Process error message here
  23:     }
  24: 
  25:     this.ViewModelState = ViewModelStateType.Default;
  26: }

Finalmente el método VerifyCalledOnUIThread de la clase ViewModel se encarga de comprobar que la llamada al método se está haciendo desde el hilo correcto

   1: [Conditional("Debug")]
   2: protected void VerifyCalledOnUIThread()
   3: {
   4:     Debug.Assert(Dispatcher.CurrentDispatcher == this.Dispatcher, "Call must be made on UI thread.");
   5: }

Está misma llamada se hace también desde otros sitios como algunas de las propiedades de ViewModel

   1: public System.String IdDivisa
   2: {
   3:     get { return this.DataModel.IdDivisa; }
   4:     set
   5:     {
   6:         this.VerifyCalledOnUIThread();
   7: 
   8:         if (this.DataModel.IdDivisa != value)
   9:         {
  10:             this.DataModel.IdDivisa = value;
  11:             this.NotifyPropertyChanged("IdDivisa");
  12:         }
  13:     }
  14: }

Responder

Por favor, inicia sesión con uno de estos métodos para publicar tu comentario:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s