Gerardo Contijoch

Experiencias del día a día trabajando con .NET – ASP.NET, C#, ASP.NET MVC y demas…

Problema con requests asíncronos en Silverlight 2

Posted by Gerardo Contijoch en febrero 28, 2009

Ayer fue uno de ‘esos días’ en el trabajo. Con ‘esos días’ me refiero a aquellos en los que uno pierde todo el día resolviendo (o al menos intentando resolver) un pequeñísimo inconveniente y no puede dar con la solución a pesar de estar convencido de que hay una. Uno esta a un paso de resolver todo, pero siempre hay un pero. En mi caso fue un pero grande, al punto en que no estoy seguro hasta donde vale la pena seguir el desarrollo de lo que estaba haciendo.

Espero que este post sirva de advertencia para los que deseen hacer algo similar a lo que hice yo y les ahorre bastantes horas de pruebas e investigación.

Lo que yo necesito hacer es una clase (dentro de una librería de clases Silverlight) que básicamente se comporte como un proxy a un WebService (cumple otras funciones este proxy, pero nos alcanza con saber que se conecta con un WebService). Como mi intensión es ocultar del usuario la verdadera implementación (es decir, la llamada al WebService en si), mi idea era consultar sincrónicamente este WebService cuando el usuario llame a un método y devolverle la respuesta en el momento, como si no hubiese WebService en el medio. Rápidamente encontré que no podía. Paso a explicar.

El problema está en como se comportan la clases HTTP (WebClient y HttpWebRequest en mi caso, pero lo mismo ocurre con los proxies a WebServices por ejemplo) en Silverlight.

Problema 1: Requests asincrónicos

En una aplicación WinForm o un site común las clases HTTP que vienen en el framework permiten hacer tanto requests sincrónicos como asincrónicos (por ejemplo, WebClient posee los métodos DownloadString y DownloadStringAsync), pero en Silverlight (que tiene una versión especial del Framework), solo se permite hacerlos de la última manera. Esto generó algunas quejas en la comunidad de programadores, pero parece que no va a cambiar ya que Microsoft se propuso respetar un estándar (NPAPI) que exige que estas llamadas sean hechas de manera asincrónica. Como Silverlight es un plugin para los browsers, el mismo necesita usar las APIS expuestas por este, las cuales están definidas por NPAPI (por lo menos en los browsers que respetan el estándar).

Así que me vi obligado a simular una operación sincrónica que por detrás funciona asincrónicamente. Para ello hice algo similar a esto (dejando de lado detalles como el manejo de excepciones y timeouts):

   1: public string Request() {
   2:
   3:     string respuesta = "";
   4:
   5:     WebClient client = new WebClient();
   6:
   7:     // Usamos este objeto para saber cuando se recibio la respuesta a nuestro request
   8:     AutoResetEvent arDownloadCompleto = new AutoResetEvent(false);
   9:
  10:     // Handler para procesar la respuesta
  11:     // Noten que nos limitamos a guardarla y a notificar que la recibimos
  12:     client.DownloadStringCompleted += delegate(object o, DownloadStringCompletedEventArgs ev) {
  13:         respuesta = ev.Result;
  14:         arDownloadCompleto.Set();
  15:     };
  16:
  17:     // Comenzamos el request (en este ejemplo, bajamos el HTML de www.example.com)
  18:     client.DownloadStringAsync(new Uri("http://www.example.com", UriKind.Absolute));
  19:
  20:     // Esperamos a que el método Set() sea llamado (esto deberia ocurrir cuando se complete la bajada)
  21:     arDownloadCompleto.WaitOne();
  22:
  23:     /* ...
  24:      * Procesamos la respuesta...
  25:      * ...
  26:      */
  27:
  28:     // Devolvemos la respuesta
  29:     return respuesta;
  30: }

La sincronicidad nos la da el objeto AutoResetEvent que en la línea 21 detiene la ejecución del hilo hasta que se haya completado la descarga.

Problema 2: Request solo en el UI Thread

Sin embargo este código no funciona debido a un pequeño detalle de la clase WebClient: el callback DownloadStringCompleted se ejecuta siempre en el UI Thread. En la mayoría de los casos eso es una buena noticia ya que podemos interactuar con la IU sin necesidad de estar usando el Dispatcher, pero en nuestro caso no lo es. Este comportamiento para nosotros significa que la ejecución de este método queda detenida en la línea 21, ya que nunca va a ejecutarse el callback debido a que el hilo esta ocupado deteniéndose a la espera del callback (sí, tiene sentido lo que dije, reléanlo).

Si ésa línea 21 hubiera sido la siguiente

// Esperamos 5 segundos como máximo para evitar un lockeo del hilo
arDownloadCompleto.WaitOne(5000);

hubiese continuado el proceso (luego de 5000 milisegundos), pero la variable respuesta hubiese estado vacía y el callback solo se hubiese ejecutado luego de que el UI Thread se haya liberado.

Como consecuencia de esto decidí darle una oportunidad a la clase HttpWebRequest (que, por cierto, es usada internamente por WebClient), que según leí no tiene esa limitación. Resulta sí la tiene, pero se presenta de otra manera. En el caso de HttpWebRequest, el callback se ejecuta efectivamente en un thread diferente al del código en el cual ejecutamos BeginGetResponse (una forma primitiva de DownloadStringAsync), pero ahora el problema no esta en el momento de ejecutar el callback, sino que esta al ejecutar el request en sí (algo que no vemos nosotros). Si bien uno puede crear hilos en Silverlight (y hacer requests asincrónicos en esos otros hilos), sólo el UI Thread es el que se comunica con el browser (¿recuerdan las APIs de NPAPI que expone el browser?) y por ello el request en sí se ejecuta en el UI Thread, independientemente de que thread vaya a ejecutar el callback. Una vez mas, tenemos un deadlock en la línea en que llamamos a WaitOne().

En definitiva, no encontré todavía manera de exponer una API sincrónica que funcione por dentro de modo asincrónico en Silverlight, pero si a alguien se le ocurre algo, no dude en dejar un comentario o contactarse conmigo.

Al mal tiempo, buena cara

Lo único positivo de perder un día entero con este tema es que aprendí mucho sobre el tema y sobre el funcionamiento interno de los requests, algo que me resulto interesantísimo (¡que nerd!), así que podría decirse que en realidad el día no estuvo del todo perdido :P

Nos vemos en el próximo post!

Referencias:

Publicado originalmente en https://gerardocontijoch.wordpress.com.

2 comentarios to “Problema con requests asíncronos en Silverlight 2”

  1. […] Link al post original (via Codear, pero te lleva al original en la mayoria de los casos) Tomado del Feed de Codear […]

  2. juan carlos said

    Buenos días Gerardo,
    Es cierto que el Framework desarrollado para Silverlight no permite las llamadas sincronas a un WebService, pero para este problema existe una solución. Dicha solución consiste en utilizar el objeto XmlHttpRequest (supongo que lo conoces). Si buscas por la web encontrarás la solucion a tu problema. Cualquier problema duda o explicación que tengas no dudes en contactar conmigo.

    Hasta la próxima!!

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

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

 
A %d blogueros les gusta esto: