Acceso al UI desde un hilo secundario en Silverlight

Todos los que hemos trabajado con aplicaciones Windows y en las cuales hemos necesitado interactuar con el UI desde un hilo secundario sabremos que esto requiere de un tratamiento especial.

Hay una regla de oro cuando trabajamos WinForm e hilos: Con el UI solo se interactúa desde el hilo principal de la aplicación.

Ayer publiqué un artículo sobre un trabajo que vengo haciendo para conectar un servicio y un cliente Silverlight usando Socket. Los sockets usan comunicación asincrónica por lo que el evento de lectura de datos sobre el puerto ocurre en hilos secundarios que son creados por la conexión para notificar a mi aplicación que tenemos información nueva que tratar.

Código que inicializa y conecta el sockets en Silverlight:

DnsEndPoint endPoint = new DnsEndPoint(
       
Application.Current.Host.Source.DnsSafeHost, 4530);

Socket
socket = new Socket(AddressFamily.InterNetwork,
       
SocketType.Stream, ProtocolType.Tcp);

SocketAsyncEventArgs args = new SocketAsyncEventArgs();
args.UserToken = socket;
args.RemoteEndPoint = endPoint;
args.Completed += new EventHandler<SocketAsyncEventArgs>(OnSocketConnectCompleted);

socket.ConnectAsync(args);

Aquí podemos ver sin problemas la llamada al método ConnectAsync y la asignación del evento Completed que será lanzado por el socket en el momento en que la conexión o la lectura de datos sean completadas.

La información que me llega desde el servidor es transformada en objetos que son pintados dinámicamente en mi aplicación Silverlight. Esta operación es realizada en el método InitializeExtensionList:

private void OnSocketReceive(object sender, SocketAsyncEventArgs e)
{
  
var data = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
  
//Get initial team data
  
if (_extensions == null)
  
{
     
InitializeExtensionList(data);
  
}

   //Prepare to receive more data
  
Socket socket = (Socket)e.UserToken;
  
socket.ReceiveAsync(e);
}

Pues bien, al intentar ejecutar mi aplicación me encuentro con el siguiente mensaje:

Ummm… Invalid Cross Thread Access: La excepción es UnauthorizedAccessException. El mensaje de error habla por sí solo. Se ha denegado el acceso al UI desde un hilo que no es el principal dentro de mi aplicación.

En aplicaciones Windows esto se resolvía mediante un delegado que me permitiera sincronizar el acceso al UI con el hilo principal de la aplicación.

Ejemplo en Windows Form:

EventHandler m_progress = delegate
{
  
_frmProgress.ShowQuality(userData.ToString());

};

Invoke(m_progress);

El código dentro del delegado interactúa con el UI, la sincronización con el hilo principal se realiza mediante el método Invoque.

Pero… ¿Cómo Silverlight me permitirá hacer esto?

Pues por un lado tenemos la clase Dispatcher, la cual proporciona los servicios que me permiten manejar los elementos de trabajo de un subproceso. En otras palabras, ofrece compatibilidad para ejecutar código en el subproceso de interfaz de usuario de un subproceso que no es de interfaz de usuario. (Just in time! Esto me salva la vida.)

Esta clase tiene un método llamado BeginInvoke el cual recibe como parámetros un delegado y una matriz de valores que se pasan como argumentos (opcional).

Pues nada, que la historia de sincronización entre hilos secundarios y el hilo principal de la interfaz de usuario que tan común es en Windows Form, sobre todo cuando trabajamos con SmartClient, se repite en Silverlight. 😉

La forma correcta para ejecutar mi código sería:

private void OnSocketReceive(object sender, SocketAsyncEventArgs e)
{
  
var data = Encoding.UTF8.GetString(e.Buffer, e.Offset, e.BytesTransferred);
  
//Get initial team data
  
if (_extensions == null)
  
{
     
Dispatcher.BeginInvoke(() => InitializeExtensionList(data));
  
}

   //Prepare to receive more data
  
Socket socket = (Socket)e.UserToken;
  
socket.ReceiveAsync(e);
}

Seguimos 😉

Deja un comentario

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