Cómo crear un temporizador global en ASP.NET

metronomeLas páginas Web son objetos con un tiempo de vida corto: se llaman, se ejecutan en el servidor, se devuelve el resultado y mueren. Por lo tanto no tiene sentido colocar en ellas un objeto como un temporizador (el típico Timer que sí tenemos en un formulario de Windows).

La forma de atar un evento periódico a una página Web (por ejemplo para actualizar los contenidos de la misma) es siempre en en lado cliente, es decir, con JavaScript. Para facilitarnos la vida, las extensiones de ASP.NET AJAX incluyen un control Timer que podemos arrastrar sobre el formulario para forzar la actualización periódica de uno o más UpdatePanels que tengamos sobre el mismo.

Sin embargo, en principio, no tenemos forma de conseguir un temporizador en el servidor que se ejecute cada cierto tiempo para todos los usuarios de nuestra aplicación. ¿O sí?

Crear un temporizador para una aplicación Web

Dentro de las clases base de la plataforma .NET, dentro del espacio de nombres System.Timers, existe una clase especial llamada Timer que sirve para crear temporizadores globales y está orientado a entornos de servidor (para requerimientos menos estrictos también existe la clase System.Threading.Timer, que es completamente distinta y no se adapta tan bien a nuestro caso).

Podemos usar esta clase para crear un temporizador global que funcione permanentemente en nuestra aplicación Web. Para ello podemos usar Global.asax para inicializarlo o, mejor aún, crear un pequeño módulo temporizador que podamos añadir a voluntad a nuestra aplicación sin necesidad de cambiar el código. Es lo que vamos a hacer.

Para el ejemplo vamos a crear un nuevo módulo HTTP con C# que implemente, como ejemplo sencillo, un contador global que se incremente cada segundo por medio de un temporizador.

El código es el siguiente:

using System;
using System.Web;
using System.Timers;

/// 
/// Summary description for ModuloTemporizador
/// 
public class ModuloTemporizador : IHttpModule
{
    // Variable que guarda una referencia al Timer (la misma para todas las instancias del módulo)
    static Timer _timerGlobal = null;
    static HttpApplication _App;

    public void Init(HttpApplication context)
    {
        if (_timerGlobal == null)
        {
            //Inicializo el contador
            context.Application.Lock();
            context.Application["Contador"] = 0L;
            context.Application.UnLock();

            //Guardo la referencia a Application
            _App = context;

            //inicializo el Timer
            _timerGlobal = new Timer(1000);
            _timerGlobal.Elapsed += new ElapsedEventHandler(ProcesoPeriodico);
            _timerGlobal.Start();
            
        }
    }

    public void Dispose()
    {
        _timerGlobal = null;
    }

    /// 
    /// Aquí se procesa periódicamente el Timer
    /// 
    private static void ProcesoPeriodico(object source, ElapsedEventArgs e)
    {
        _App.Application.Lock();
        long contador = (long)_App.Application["Contador"];
        _App.Application["Contador"] = contador + 1;
        _App.Application.UnLock();
    }

}

Se trata de un modulo HTTP normal, que implementa como todos la interfaz IHttpModule. Declara dos variables estáticas (serían Shared en VB) que permiten guardar sendas referencias al temporizador y al objeto Application, que nos permitirá acceder a las propiedades del mismo.

En el evento de inicialización la primera parte es sólo para nuestro ejemplo. Inicializa una variable de aplicación (común a todos los usuarios) que utilizaremos como un contador para verificar que el temporizador funciona. En condiciones normales no haríamos nada similar, pero a efectos de este ejemplo bien nos vale.

Después se establece la variable estática_App para tener acceso al contexto de aplicación desde toda la clase, y poder manejarlo desde otros métodos.

La parte interesante es la de inicialización del Timer, aunque no tiene tampoco ninguna dificultad: se declara un Timer pasándole el intervalo en su constructor. En este caso se le pasan 1000 milisegundos, o sea, un segundo, de forma que el temporizador hará «Tick» cada segundo. Se establece un manejador para su evento Elapsed, que se llama automáticamente cada vez que pase el periodo establecido y es donde realmente llevaremos a cabo la tarea periódica. Finalmente Se inicia el temporizador llamando a su método Start().

En el manejador del evento podemos llevar a cabo cualquier  tarea periodica que necesitemos: comprobar unos valores y refrescarlos, verificar que haya nuevos datos en una cola, anotar una información de traza interna que necesitemos para la aplicación, etc… Cualquiera que sea tu necesidad. En este ejemplo simplemente aumentamos el valor del contador.

Es posible detener el temporizador llamando a su método Stop() o cambiar el intervalo de notificación modificando el valor de la propiedad Interval.

Probando el temporizador

Para probar el temporizador y verificar que funciona he creado una pequeña página Default.aspx que el único código que contiene es este:

<%= Application("Contador") %>

Con esto lo que hacemos es mostrar el valor del contador por pantalla.

Si lanzamos la aplicación y llamamos a Default.aspx, refrescando sus contenidos con F5 en el navegador cada pocos segundos veremos…. ¡que no funciona!

El motivo es que hemos creado el módulo pero no lo hemos registrado con ASP.NET y por lo tanto no se ha usado.

Para registrar el módulo y que entre en funcionamiento nuestro temporizador sólo es necesario incluir esto en nuestro web.config:

<configuration>
	<system.web>
           <httpModules>
              <add name="ModuloTemporizador" type="ModuloTemporizador"/>
           </httpModules>
	</system.web>
</configuration>

O bien, si estamos trabajando en el modo integrado de IIS 7.x, este equivalente en el web.config del raíz:

<configuration>
	<system.webServer>
           <modules>
              <add name="ModuloTemporizador" type="ModuloTemporizador"/>
           </modules>
	</system.webServer>
</configuration>

Con esto ya podemos lanzar de nuevo la aplicación. Ahora, si refrescamos la página, veremos que el contador va aumentando en una unidad cada segundo. Si se conectaran varios usuarios diferentes 8en diferentes sesiones) veremos que el contador se les actualiza del mismo modo, y que no se detiene aunque cerremos todas las sesiones, ya que sólo parará si llamamos a Stop() o si la aplicación se cierra.

Esto último es muy importante: nuestro temporizador sólo funcionará mientras la aplicación Web esté funcionando. Es decir, que no se trata de un temporizador que se ejecuta todo el tiempo, desde que arranca el servidor, sino que mientras no se reciba una primera llamada a la aplicación y por lo tanto esta se inicie tampoco se iniciará el temporizador. Si por cualquier motivo (reciclado del grupo de aplicaciones, descarga de la memoria porque no hay usuarios, etc..) la aplicación se detiene también lo hará el temporizador.

No obstante este método es muy interesante para ser utilizado con necesidades particulares sobre la disponibilidad de un evento periódico global en nuestras aplicaciones web.

He dejado el ejemplo para descarga aquí.

¡Espero que te resulte útil!

Aprende .NET, Cursos on-line tutelados:
   ·
Desarrollo Web con ASP.NET 4.0 Web Forms (Tutelado por mi)
   · ASP.NET 4.0 Web Forms desde cero (Tutelado por mi)
   · Desarrollo Web con ASP.NET MVC 2
   · Silverlight 4.0 – Aplicaciones Ricas para Internet (RIA)
   · jQuery paso a paso para programadores ASP.NET
   · Visual Studio 2010 desde cero

Sin categoría

One thought on “Cómo crear un temporizador global en ASP.NET

  1. Me ha resultado muy interesante. Y justamente busco un código para hacer lo siguiente, a ver si pudieras orientarme.

    Tengo una aplicación en VB.NET con un cronómetro que muestra el tiempo de un evento en un formulario en otro monitor. El caso es que me gustaría ver el dato de ese cronómetro en un dispositivo móvil y mi idea inicial era hacerlo via webserver o similar, atacando a la IP:puerto del ordenador donde está la aplicación VB.NET. Pero lo ideal es ver el tiempo que queda real independientemente de cuando lo consulte via web. Si pudieras orientarme me harías un gran favor.

Deja un comentario

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