Performance

Como eliminar la excepcion ThreadAbortException al navegar entre paginas en ASP.NET

Llevo casi 10 años trabajando con ASP.NET y me sorprendi al aprender que cada vez que se ejecuta el comando Response.Redirect(url) en ASP.NET (para navegar hacia otra pagina), el framework lanza intencionalmente una excepcion de tipo ThreadAbortException, con el unico objetivo de terminar absolutamente la ejecucion del request actual.

Como mencione, esto es intencional por parte del framework, y el articulo “Correct use of System.Web.HttpResponse.Redirect” de Thomas Marquardt explica las razones de este diseño.

Para observar esto, solo se necesita correr una aplicacion de ASP.NET en Debug Mode, y ejecutar una linea de Response.Redirect(“Default.aspx”), y observar la ventana de Output en Visual Studio; se vera lo siguiente:

A first chance exception of type ‘System.Threading.ThreadAbortException’ occurred in mscorlib.dll

An exception of type ‘System.Threading.ThreadAbortException’ occurred in mscorlib.dll but was not handled in user code 

Como la mayoria de los desarrolladores debe saber, esto es un problema porque uno de los principales lineamientos de performance en .NET es “No utilizar Exceptions para controlar el flujo de la aplicacion“. Si esto sucede en una aplicacion web con mucho trafico, el lanzar exceptions cada vez que un usuario navega de una pagina a otra, eventualmente va a afectar el desempeño de la aplicacion.

En fin, la solucion para esto es sencilla, pero hay un detalle importante que hay que tomar en cuenta. Como el articulo de Thomas M. indica, se debe de utilizar el siguiente codigo:

Response.Redirect("Target.aspx", false);

HttpContext.Current.ApplicationInstance.CompleteRequest();

Es decir, utilizamos el overload de Response.Redirect pasando false al parametro de endResponse(para evitar que el .NET framework haga la llamada interna a Response.End(), y al final indicamos que deseamos completar/finalizar el request.

El detalle importante que hay que considerar si deseamos utilizar esta manera de redireccionar para evitar la excepcion es que antes, la llamada interna a Response.End() hacia que cualquier codigo que estuviera despues de la llamada original a Response.Redirect() ya no se ejecute, y esto es algo a lo que inconscientemente muchos desarrolladores nos acostumbramos. No es nada raro ver codigo de este tipo en aplicaciones de ASP.NET:


if (!User.HasAccess()) Response.Redirect("GetOut.aspx");

SinceUserHasAccessDoThis();

Si lo hacemos de la manera nueva, pasando el overload con false, cualquier codigo en el metodo que este despues del Response.Redirect si se ejecutara.

Esto es porque era la llamada interna a Response.End() (que ahora estamos evitando) que terminaba la ejecucion del codigo. Como ahora estamos evitando esa llamada, la funcion SinceUserHasAccessDoThis() si se ejecutaria, a pesar de que el usuario no tuviera el acceso que se verifica en el metodo HasAccess().

Aparte de lograr evitar esas excepciones, aprendi que tambien es incorrecto utilizar Response.Redirect para controlar el flujo de la aplicacion, y al menos yo lo hacia inconscientemente. Si se desea evitar esas Exceptions y no se tiene el tiempo para hacer un refactor del codigo para estructurarlo correctamente, se puede arreglar utilizando un return; despues del Response.Redirect, de esta manera:


if (!User.HasAccess())
{
    Response.Redirect("GetOut.aspx", false);
    HttpContext.Current.ApplicationInstance.CompleteRequest();
    return;
}

SinceUserHasAccessDoThis();

Paginas de referencia

  1. Response.Redirect causes System.Threading.ThreadAbortException
  2. Correct use of System.Web.HttpResponse.Redirect
  3. Microsoft Support: PRB: ThreadAbortException Occurs If You Use Response.End, Response.Redirect, or Server.Transfer
  4. HttpApplication.CompleteRequest Method
  5. Is Response.End() considered harmful?
  6. Rick Strahl – Ending a Response without Response.End() Exceptions?

Espero les sirva.

Como determinar el tamaño de los objetos en Session State en una aplicacion de ASP.NET

En el proyecto en el que estoy asignado actualmente, trabajamos en una aplicacion financiera (ASP.NET WebForms) muy grande. Utilizada en varios paises por miles de usuarios, siempre estamos buscando optimizar la aplicacion lo mas que podamos.

El caso de este post en especifico, es que notamos que habia muchos objetos almacenados en Session State y teniamos curiosidad de saber el tamaño que esos objetos estaban ocupando, para saber si era un area que debiamos optimizar. En la actualidad, la memoria RAM es de los componentes mas baratos y una de las maneras mas faciles de mejorar el desempeño de una aplicacion, pero si acaso una empresa, aplicacion o equipo esta limitado en presupuesto, igual es un area que se puede mejorar.

Como siempre, en StackOverflow.com ya habia alguien que habia tenido la misma duda que nosotros, y pudimos obtener un metodo que serializa a disco los contenidos de la sesion para poder medir su tamaño. Este es el metodo:

protected void PrintSessionSize()
{
 long totalSessionBytes = 0;
 var b = new System.Runtime.Serialization.Formatters.Binary.BinaryFormatter();
 System.IO.MemoryStream m;

 foreach (var obj in HttpContext.Current.Session)
 {
     m = new System.IO.MemoryStream();
     b.Serialize(m, obj);
     totalSessionBytes += m.Length;
 }
}

Espero que les sirva.

Como medir tiempo de ejecucion de codigo en C# y .NET

Proximamente voy a hacer un comparativo de performance entre Entity Framework y Subsonic, así que se me ocurrió de una vez escribir este post sobre la manera correcta en que se debe medir el tiempo de ejecución de un cierto bloque de código utilizando C# y .NET.

La medición del tiempo se hace usando una instancia de la clase StopWatch, que se encuentra en el namespace de System.Diagnostics. La clase StopWatch provee un conjunto de métodos y propiedades que pueden ser usados para medir con alta precisión el tiempo transcurrido entre intervalos.

Los métodos disponibles sobre esta instancia serían:

  • Start() - inicia el contador a partir del último lapso. La cuenta es acumulativa, es decir, si se verifica la propiedad ElapsedMilliseconds después de 4 llamadas a los métodos de Start y Stop, el tiempo desplegado será el acumulado de los 4 intervalos.
  • Stop() - detiene momentaneamente el timer.
  • Reset() - reinicia los valores almacenados del timer

Las propiedades disponibles, que usaríamos para desplegar el tiempo que tomó alguna rutina, serían:

  • Elapsed - Regresa una instancia de tipo TimeSpan que determina el tiempo que ha transcurrido desde que se llamó al método Start() por primera vez.
  • ElapsedMilliseconds – Regresa un valor de tipo long que indica los milisegundos transcurridos desde la primera vez que se llamó al método Start().
  • ElapsedTicks - Regresa un valor de tipo long, que indica los Ticks que han pasado desde la primera vez que se llamó al método Start(). Nota importante respecto a esta propiedad: Los ticks de un StopWatch no son iguales a los ticks de un DateTime. En DateTime, cada tick representa un intervalo de 100 nanosegundos. En StopWatch, un tick representa un intervalo de tiempo equivalente a 1 segundo dividido entre la propiedad Frequency del StopWatch.
  • IsRunning - determina si el timer está activo y contando Ticks.

Las propiedades IsHighResolution y Frequency no son propias de una instancia StopWatch, sino de la clase StopWatch.

IsHighResolution indica si el timer utilizado está basado en un contador de performance de alta resolución. Esto depende del procesador/arquitectura de la computadora en la que está corriendo el código

Frequency representa el numero de StopWatch ticks por segundo.

Ahora, un poco de código de ejemplo:


Console.WriteLine(string.Format("IsHighResolution: {0}", Stopwatch.IsHighResolution ? "Yes" : "No"));
Console.WriteLine(string.Format("Frequency: {0}", Stopwatch.Frequency));
Console.WriteLine("----------------------");

var dos = "2";
var suma = 0;

//Medir int.Parse
var timer = Stopwatch.StartNew();

for (var i = 1; i <= 1000; i++)
{
suma += int.Parse(dos) * i;
}

timer.Stop();
Console.WriteLine(string.Format("Suma: {0}", suma));
Console.WriteLine(string.Format("Elapsed para int.Parse: {0}", timer.Elapsed));
Console.WriteLine(string.Format("ElapsedMilliseconds para int.Parse: {0}", timer.ElapsedMilliseconds));
Console.WriteLine(string.Format("ElapsedTicks para int.Parse: {0}", timer.ElapsedTicks));
Console.WriteLine("----------------------");

suma = 0;

//Medir Convert.ToInt32()
timer = Stopwatch.StartNew();

for (var i = 1; i <= 1000; i++)
{
suma += Convert.ToInt32(dos) * i;
}

timer.Stop();
Console.WriteLine(string.Format("Suma: {0}", suma));
Console.WriteLine(string.Format("Elapsed para Convert.ToInt32: {0}", timer.Elapsed));
Console.WriteLine(string.Format("ElapsedMilliseconds para Convert.ToInt32: {0}", timer.ElapsedMilliseconds));
Console.WriteLine(string.Format("ElapsedTicks para Convert.ToInt32: {0}", timer.ElapsedTicks));

da el siguiente resultado:

Midiendo intervalos de tiempo con la clase StopWatch

Midiendo intervalos de tiempo con la clase StopWatch

Como se puede apreciar, int.Parse es ligeramente más rapido que Convert.ToInt32().

De manera simplemente informativa…hay otra manera de medir intervalos de tiempo que mucha gente usa, y NO es correcto ni recomendable. Lo hacen asignando el valor de DateTime.Now a una variable de tipo DateTime y luego lo resta al valor de DateTime.Now en el momento que quieren dejar de medir.

Hay todavía otra manera similar al uso de DateTimes, que es usando la propiedad Environment.TickCount.Esto es incorrecto por tres razones:

1. Si existe una clase especializada para hacer esto en el framework, digase StopWatch, hay que usarla.
2. Environment.TickCount es signed (puede representar valores negativos también) lo cual implica que después de 25 dias, se va a entrar al rango negativo y se tendría que eliminar el bit del símbolo para poder hacer calculos correctos. Después de 50 dias, se llenarían todos los bits de la variable y la cuenta se reiniciaría. Ver discusión en StackOverflow.com donde se menciona esto.
3. En palabras del mismo Scott Hanselman, y mencionado en el mismo link de la razon #2: As an unrelated aside, if you DO use DateTime for Date-related math calculations, always use DateTime.UtcNow as DateTime.Now is susceptible to Daylight Savings Time…your calculations could be off by a hour, or worse, negative numbers.

Es decir, es muy facil que a la gente se le olvide que si va a cronometrar de esta manera, usar DateTimes para medir intervalos puede ser susceptible a los conocidos “horarios de verano”, que desfazan por temporadas el reloj por +- 1 hora. Cualquiera de las dos alternativas a StopWatch (DateTime o Environment.Ticks) es relativamente riesgosa para ser útil.

Links de Referencia:

Clase StopWatch @ MSDN

No usar el metodo IEnumerable.Count de LINQ

.NET Logo

.NET Logo

Los que usen LINQ, especificamente LINQ to SQL, favor de leer el siguiente articulo:

IEnumerable.Count() is a Code Smell

El articulo explica que es muy comun para cualquier desarrollador utilizar este metodo para hacer validaciones del tipo:

if(products.Count() == 0) ... 

El problema es que el metodo Count() de IEnumerable es un extension method que no va a hacer short-circuit al encontrar más de un item al ser usado en el contexto del ejemplo anterior.

Peor aun, como la ejecución de LINQ To SQL es deferida, los resultados del metodo Count() no se quedan en cache y cualquier nueva llamada a Count() vuelve a iterar todos los elementos de la coleccion. Tomando en cuenta que LINQ To SQL esta accesando una base de datos…es muy mal performance.

El articulo sugiere usar como alternativa el metodo .Any(). que produce un SQL mas optimizado.

Y si se necesita hacer llamadas posteriores al Count() o Any(), se recomienda eliminar la deferred execution pasando los resultados del query de LINQ a una Lista, de la siguiente manera:

IList<Product> products = GetProducts().ToList();

Para detalles completos, favor de leer el articulo original, del blog de Lee Richardson, Rapid Application Development

 Scroll to top