Sincronización de hilos

Hola!

Hoy voy a hacer un artículo sobre sincronización de hilos. He visto alguna que otra vez una forma poco correcta de hacer que un hilo padre espere a que su hijo haya terminado (como quedarse en un bucle con un Sleep y comprobar periódicamente si el hilo hijo está vivo), así que voy a explicar una forma muy sencilla de coordinar los hilos.

Como ejemplo podemos usar un servicio de Windows, ya que en estos servicios, la creación de un hilo suele ser casi imprescindible, porque lo normal es que cuando el servicio arranca nosotros generalmente querremos que el servicio se quede activo, pero debemos salir del método OnStart del servicio tan pronto como sea posible para evitar un Timeout en el arranque.

Además, sería interesante que cuando paramos el servicio, en el método OnStop del servicio, el hilo principal espere a que el hilo hijo termine aquello que esté haciendo en ese momento, para no cortar alguna operación que se pueda estar realizando cuando se quiere parar el servicio.

Para conseguir esto, en nuestra clase del servicio podemos declarar un miembro que contenga un WaitHandle en el que almacenaremos si nuestro hilo hijo ha terminado su ejecución. Este WaitHandle es el que usará el hilo padre para esperar al hilo hijo.

Una breve descripción de lo que haremos en el servicio será lo siguiente:

  • Al arrancar el servicio, crearemos un hilo nuevo en el ThreadPool, usando el método QueueUserWorkItem, al que le pasaremos el WaitHandle que hemos definido en la clase. Como WaitHandle es una clase abstracta, podemos usar (por ejemplo) un AutoResetEvent.
  • Al parar el servicio, esperaremos a que el hilo hijo que creamos al arrancar el servicio termine su ejecución. Para ello usaremos el método estático WaitAll de la clase WaitHandle, pasándole como argumento el objeto estático de tipo WaitHandle que usamos al arrancar el servicio.
  • En el método que ejecuta el hilo hijo debemos:
    • Recibir el objeto WaitHandle (AutoResetEvent) que nos pasa el hilo que nos ha creado, y almacenarlo.
    • Agrupar la ejecución del servidor en un bloque try { … } finally { … } (se puede-debe poner un catch), y en el finally deberemos notificar que hemos terminado la ejecución, llamando al método Set del objeto AutoResetEvent que hemos recibido, así nos aseguramos de que notificaremos que hemos terminado la ejecución del hilo hijo al hilo padre.

Bueno, pues ésta es la idea básicamente.

Saludos! 

Los Servicios Web .NET no se llevan bien con VB 6

Hola!

Lo primero, feliz año nuevo a todos. (un poco tarde, ¿verdad?)

Hoy voy a tratar un tema un tanto puñetero, que no debería darse nunca en un mundo ideal en el que todo el mundo escribiese código manejado y no hubiese que utilizar componentes COM antiguos hechos con Visual Basic 6 (o cualquier otra tecnología que sea COM y utilice el modelo STA).

¿Por qué los servicios web .NET no se llevan bien con Visual Basic 6? Pues por una razón bastante sencilla. Supongamos que tenemos el escenario propuesto, es decir, un servicio web .NET que utiliza una dll programada con Visual Basic 6. Supongamos también que este servicio se utiliza muy a menudo, de manera que tenemos varias llamadas concurrentes.

Visual Basic 6 utiliza siempre un modelo STA para la generación de sus componentes, y no se puede bajo ningún concepto generar componentes que usen un modelo MTA en Visual Basic 6. Esto quiere decir que cada vez que se realizamos una petición vía COM a un componente desarrollado con Visual Basic 6, esta llamada se ejecutará siempre en el mismo hilo de ejecución. Si se llama varias veces de forma concurrente a dicho objeto, COM encolará las peticiones, ya que un único hilo sólo puede procesar una petición cada vez.

Sin embargo, los servicios web .NET siempre utilizan un modelo MTA (al igual que las páginas aspx en .NET, salvo que utilicemos el atributo AspCompat). Esto quiere decir que para cada petición que realicemos a nuestro servicio, se creará un hilo diferente que llevará a cabo dicha petición. Esto lo podemos comprobar utilizando el siguiente servicio de prueba:

using System;
using System.Web;
using System.Web.Services;
using System.Threading;
 
[WebService(Namespace = "http://tempuri.org/")]
public class ServicioPrueba : System.Web.Services.WebService
{
    [WebMethod]
    public string Prueba()
    {
        return Thread.CurrentThread.ApartmentState.ToString();
    }
}

Ahora pongamos ambos modelos en correspondencia: El WebService usa MTA, y el componente usa STA. En el siguiente diagrama (obtenido del artículo de Jeff Prosise «Running ASMX Web Services on STA Threads«) podemos ver cómo se procesan las llamadas a nuestro componente COM:

 MTA -> STA

Si estuviésemos utilizando una página web, podríamos aplicar el atributo AspCompat a la directiva @Page de nuestra página web, consiguiendo que nuestra página se ejecute en un modelo STA. Sin embargo, dicho atributo NO existe para la directiva de los WebServices: @WebService.

¿Qué podemos hacer entonces? En su artículo, Jeff nos propone la siguiente solución: Crear un httpHandler, que herede de System.Web.UI.Page, y que se encargue de procesar las llamadas a los WebServices. El hecho de heredar de Page nos permitirá aprovechar toda la infraestructura creada para soportar el comportamiento que controla el atributo AspCompat (es decir, que tenemos implementado y accesible lo necesario para poder ejecutar nuestro código bajo el modelo STA).

El siguiente diagrama mostraría cómo se procesan las llamadas utilizando el httpHandler propuesto en el artículo:

STA -> STA

Tened en cuenta que esto hay que configurarlo en el web.config para que a la hora de procesar nuestros servicios se utilice el httpHandler. En el artículo lo configura para que este httpHandler se haga cargo de todos los servicios web, y normalmente eso no es lo que necesitamos, así que cuidadín, y a configurarlo correctamente. Por ejemplo, podríamos agrupar los WebServices que deben ejecutarse bajo STA en una ruta concreta, y colocar en esa ruta el fichero web.config que define el httpHandler.

Bueno, pues aquí lo voy a dejar por hoy. Saludos!