miércoles, 27 de marzo de 2013

C#: Introducción a Task async based Pattern en Visual Studio 2012

Esta semana ya he instalado

Visual Studio 2012

(algunos pensarán ya era hora jajajja). La intención es mirarme un poco el desarrollo de la nueva línea de diseño “metro” que me llama bastante. Ya os iré contando, inevitablemente algún artículo caerá jeje.

Una de las novedades interesantes que incorpora Visual Studio 2012 respecto a

C#

y vb es la gestión de hilos asíncronos basado en

TAP pattern

Task based Async pattern

. En este otro artículo ya exponíamos en

areaTIC

las ventajas de la programación asíncrona respecto programación síncrona y por otro lado vimos que una de las desventajas es que la gestión del código se complica a medida que vamos implementando asíncronos. Con este nuevo patrón se pretende simplificar la gestión de código asíncrono respecto otros patrones como IAsyncPattern.

A partir del framework 4.5 algunas clases del Framework ya incorporan métodos que implementan el patrón async y pueden usarse, como por ejemplo, System.Threading.Stream.CopyToAsync. El siguiente link de MSDN explica perfectamente cómo usar estos métodos usando el patrón TAP. En este ejemplo estamos usando un método del framework que ya está preparado para ser invocado vía TAP, a continuación veremos cómo convertir un método cualquiera para poder ser invocado vía TAP.

En principio si estamos trabajando con framework 4.5 y Visual Studio 2012 deberíamos poder añadir el modificador

async

a la firma de cualquiera de nuestros métodos, podemos hacer la prueba y tal vez recibiremos alguna advertencia pero compilará y podremos seguir usando el método una vez marcado con el modificador

async

. Hasta aquí simplemente hemos marcado un método como

async

pero no hemos hecho suficiente para garantizar que nuestro código sea asíncrono. Hacer que el código nuestro código sea asíncrono siguiendo este patrón no implica seguir siempre los mismos pasos si no que dependerá un poco del comportamiento que queramos aplicar en cada caso.

Es importante entender el objeto

Task

, ya que se encarga de controlar el estado de la llamada asíncrona y proporcionar información sobre esta al hilo “padre”. Resumiendo y simplificando mucho, podemos invocar un método (que implementa TAP) y guardarnos un objeto Task, realizar ciertas acciones y posteriormente antes de salir del método “padre” recuperar el valor de respuesta de la primera llamada usando el comando

await Task

. (en este artículo no profundizamos pero a partir de Task se puede tener acceso al progreso de la tarea, estado, excepciones, …)

Para aplicar el patrón en un escenario diferente al típico de descargar contenido de una URL o escribir un fichero en disco, partimos de un ejemplo anterior de un artículo de

areaTIC

donde se explicaba como implementar IAsyncPattern. En el ejemplo simulamos tener 3 procesos que realizan trabajos de computación pesados y vimos como hacer 3 llamadas en 3 threads diferentes para evitar tener que esperar a que finalice un proceso para que el hilo de ejecución procese el siguiente… Con

TAP

se puede llegar a hacer lo mismo, veamos un par de alternativas sobre el mismo caso de uso.

En primer lugar tenemos que hacer que los métodos Proceso1, Proceso2, Proceso3 de la clase implementen el patrón TAP es decir que devuelvan un objeto Task en este caso. Veaamos como hacerlo:
class Proceso
{
        
   public Task ProcesoNegocio1(string pEntrada)
   {
     return Task.Run(() =>
     {
        //dormimos ejecución durante 7 segundos 
        //simulando una acción que tarda ese tiempo.
        System.Threading.Thread.Sleep(7000);
        //devolvemos fecha fin proceso concatenada a la fecha de entrada
        return string.Format("Fin Proceso 1: {0}-{1}", pEntrada, DateTime.Now.ToString());
            }, new CancellationToken());
    }

    public Task ProcesoNegocio2(string pEntrada)
    {
     return Task.Run(() =>
     {
       //dormimos ejecución durante 10 segundos 
       //simulando una acción que tarda ese tiempo.
       System.Threading.Thread.Sleep(10000);
       //devolvemos fecha fin proceso concatenada a la fecha de entrada
       return string.Format("Fin Proceso 2: {0}-{1}", pEntrada, DateTime.Now.ToString());
     }, new CancellationToken());
    }

    public Task ProcesoNegocio3(string pEntrada)
    {
     return Task.Run(() =>
     {
        //dormimos ejecución durante 3 segundos 
        //simulando una acción que tarda ese tiempo.
        System.Threading.Thread.Sleep(3000);
        //devolvemos fecha fin proceso concatenada a la fecha de entrada
        return string.Format("Fin Proceso 3: {0}-{1}", pEntrada,    DateTime.Now.ToString());
     }, new CancellationToken());
   }
}
Si os fijáis en estos métodos no hemos usado el modificador async en la firma, el modificador debería tenerlo el método “padre” que llamará a estos y realizará la gestión del multithreading. Veamos el método “padre” como quedaría:
async private void button1_Click(object sender, EventArgs e)
{
  Proceso proceso = new Proceso();
  Task result1 = proceso.ProcesoNegocio1(DateTime.Now.ToString());
  Task result2 = proceso.ProcesoNegocio2(DateTime.Now.ToString());
  Task result3 = proceso.ProcesoNegocio3(DateTime.Now.ToString());
  Console.WriteLine(await result1);
  Console.WriteLine(await result2);
  Console.WriteLine(await result3);
}
A priori con esto ya haríamos que el hilo de ejecución principal procese en un thread diferente cada una de las 3 llamadas, de modo que estos realizarán el proceso y quedarán a la espera que solicitemos el resultado mediante el comando

await

.

Tal como hemos dejado el código obtendremos las respuestas por orden y mostraríamos el resultado, si os fijáis en el tiempo de entrada y fin de cada proceso que escribimos en consola veréis que la gestión ha sido asíncrona y que no necesariamente el orden de finalización de cada método coincide con el orden en que solicitamos la respuesta usando await. Podríamos haber llegado a usar IEnumarable.WhenAll para esperar a que finalicen las 3 tareas por aquello de ser más elegantes pero es un ejemplo de batalla... En definitiva esto podría llegar a complicarse bastante más de lo que hago en este el artículo. En el siguiente link se explica a fondo como tratar excepciones, progreso, cancelar, etc… con ejemplos… es uno de los 'tutoriales' más claros que he encontrado en la web (descargar documento word).

Hasta aquí el artículo de hoy se agradecen vuestros comentarios, debates, valoraciones, etc… que sino esto es muy aburrido jajaja. Como siempre recordaros que podéis seguir

areaTIC

en las redes sociales.


martes, 19 de marzo de 2013

SQL Server: Información sobre uso de índices

Debido a que los

índices

ocupan espacio (disco) y requiere un coste (cpu) mantenerlos actualizados es muy importante controlar su

uso

y si es nulo deberían ser eliminados. En este sentido es importante destacar que en ocasiones se crean

índices

para realizar procesos concretos (por ejemplo, una transferencia de datos a otro sistema) que una vez concluidos, si ya no va a volver a realizarse el proceso, podrían eliminarse.

Mediante la DMV (dynamic management view)

sys.dm_db_index_usage_stats SQL Server

nos permite ver el

uso

de los

índices

de una base de datos, veamos un ejemplo:
-- Obtenemos info sobre uso de índices definidos en tablas de usuario
SELECT OBJECT_NAME(ius.object_id) AS TableName, 
       idx.name AS IndexName, ius.*      
FROM SYS.DM_DB_INDEX_USAGE_STATS AS ius 
INNER JOIN SYS.INDEXES AS idx 
ON (idx.object_id = ius.object_id AND idx.index_id = ius.index_id)
WHERE OBJECTPROPERTY(ius.object_id,'IsUserTable') = 1 
Sobre la consulta anterior destacaremos:

  • La columna TableName devuelve la tabla sobre la que está definido el

    índice

    .

  • La columna IndexName devuelve el nombre del

    índice

    sobre el que se muestra información de

    uso

    .

  • Las columnas user_seeks, user_scans y user_lookups, como su nombre especifica en cada caso, nos indican el número de accesos de consultas de usuario para cada uno de los tipos. Si las 3 columnas anteriores tienen valor 0 es que el índice no tiene

    uso

    por lo que debería valorarse la posibilidad de eliminarlo. Es importante en este punto destacar y antes de eliminar ningún

    índice

    que si el servicio de SQL Server se reinicia todos los valores de la vista se resetean; de igual forma, si se hace un Detach de la base de datos los valores de la vista se eliminan.
  • La columna user_updates nos indica el número de INSERTs / DELETEs / UPDATEs sobre el

    índice

    para consultas de usuario.

  • Las columnas last_user_seek, last_user_scan, last_user_lookup y last_user_update nos indican la última fecha en que los correspondientes valores han sido actualizados. Si el valor es NULL es que no ha habido accesos de ese tipo, si el valor es muy antiguo quizás ese índice haya dejado de utilizarse, debería profundizarse más sobre su utilidad y posible eliminación.

  • La condición indicada en el WHERE nos sirve para comprobar sólo el

    uso de índices

    definidos sobre tablas de usuario.

Si queréis más información sobre la DMV

sys.dm_db_index_usage_stats

podéis acceder a este enlace del MSDN. Y hasta aquí el artículo de hoy, espero que os sirva para mejorar el rendimiento de vuestros índices. Recordad también que dentro de

areaTIC

podéis encontrar otros artículos, no dudéis en consultar nuestro archivo, también podéis seguirnos por RSS o las principales redes sociales (twitter, facebook, linkedin...) 


LECTURAS RELACIONADAS RECOMENDADAS POR AREATIC.NET

martes, 12 de marzo de 2013

ASP.NET: Cómo personalizar la seguridad "Forms" para usar nuestra tablas de usuarios-roles.

Hoy veremos como personalizar algunos aspectos de la gestión de accesos, roles y usuarios en nuestro site

ASP.NET

. Antes de nada comentaremos sin entrar en detalle las posibilidades que ofrece

ASP.NET

para autentificar y autorizar a los clientes de un site:
  • Forms: Recomendado si los usuarios del site no se encuentran bajo un mismo dominio y el site contiene partes públicas.
  • Passport
  • Windows: Recomendado cuando los usuarios del site se encuentran bajo el mismo dominio.
Podemos especificar que tipo de seguridad queremos aplicar vía archivo web.config o bien usando la herramienta de administración que proporciona ASP.NET desde el menú Proyecto.

areaTIC, ASP.NET, Menú Proyecto Visual Studio

areaTIC, ASP.NET, Herramienta Configuración ASP.NET

Si vamos a la pestaña seguridad allí se explica perfectamente el medio de almacenamiento por defecto que se usa para la información de usuarios y roles. En resumen, por defecto la información si no hemos cambiado el provider se almacena en una instancia de SQLEXPRESS del servidor.

Llegado a este punto, ¿qué pasa si la empresa donde estamos configurando el site dispone de más aplicaciones donde hay una base de datos ya con tablas para la gestión personalizada de usuarios/roles y queremos aprovechar el mismo formato y contenido de las tablas?

A continuación veremos como configurar nuestro areaTICAuthenticationModule personalizado que se encargará de realizar la gestión de accesos y permisos en cada petición que llegue al servidor.

En primer lugar desde la página de login tendremos que generar un

FormsAuthenticationTicket

que contendrá la información del usuario y sus roles. En el ejemplo, trabajaremos con un modelo de información como el que se muestra en la imagen. Al hacer login suponemos que el servidor conecta a la base de datos donde está la información de usuarios y devuelve un objeto LoginReponse con la información del usuario y sus roles.

areaTIC, ASP.NET, Menú Proyecto Visual Studio

Veamos como generar el

FormsAuthenticationTicket

desde nuestra página de login.
FormsAuthenticationTicket authTicket = null;

  // Ojo porque si los accesos superan los 1000 caracteres se pierde la información.
  string[] userData = (from RolUsuario r in LoginResponse.Roles select r.Rol).Distinct().ToArray<string>();
  
  string authTicketUser = "";

  if (LoginResponse.Usuario != null)
    authTicketUser = LoginResponse.Usuario.Usuario;

  authTicket = new FormsAuthenticationTicket(0, authTicketUser, DateTime.Now, DateTime.Now.AddMinutes(20), false, String.Join(";", userData));
  
  string crypTicket = FormsAuthentication.Encrypt(authTicket);

  HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, crypTicket);
  HttpContext.Current.Response.Cookies.Add(authCookie);
  HttpContext.Current.Response.Redirect(FormsAuthentication.GetRedirectUrl(authTicketUser, false), false);
Antes de implementar el module estaría bien guardarnos en un objeto de aplicación la relación RolPagina para evitar tener que consultarla en base de datos cada vez que se realice una petición.
  public class Global : System.Web.HttpApplication
  {
    void Application_Start(object sender, EventArgs e)
    {
      List<RolPagina> RolPagina = ...;
      Application["RolPagina"] = RolPagina;
    }
  }
Veamos ahora como implementar el module.
namespace areaTIC.Code.Modules
{
 public sealed class areaTICAuthenticationModule : IHttpModule
 {
   HttpApplication app = null;

   public void Dispose()
   {

   }

   public void Init(HttpApplication context)
   {
     this.app = context;
     app.AuthorizeRequest += new EventHandler(this.OnAuthorize);
     app.AuthenticateRequest += new EventHandler(this.OnAuthenticate);
   }

   private void OnAuthorize(object sender, EventArgs e)
   {
     Configuration config = WebConfigurationManager.OpenWebConfiguration("/appSettings");
     //Obtenemos la lista de RolesPagina que del objeto de aplicación que hemos cargado en el paso anterior.
     List<RolPagina> RolesPorPagina = (List<RolPagina>)HttpContext.Current.Application["RolPagina"];

     if (RolesPorPagina != null)
     {
        List<Rol> Roles = (from RolPagina Roles in RolesPorPagina 
           where Roles.Pagina.Equals(HttpContext.Current.Request.Path)
           select Rol).ToList<Rol>();

        if (Roles.Count > 0)
        {
           //Si no está autentificado lo enviamos al login
           if (HttpContext.Current.User == null || !HttpContext.Current.User.Identity.IsAuthenticated)
              HttpContext.Current.Response.Redirect(string.Format("~/PaginaLogin.aspx?ReturnURL=~{0}", HttpContext.Current.Request.Path));

           IPrincipal principal = HttpContext.Current.User as IPrincipal;
           
           foreach(Rol r in Roles)
           {
             //Comprobamos si tiene asignado algún rol con acceso a la página solicitada.
             if (principal.IsInRole(r)
               HttpContext.Current.Response.Redirect(HttpContext.Current.Request.Path);
           }
           
           //si llegamos a este punto quiere decir que no dispone del rol para acceder a la página solicitada.
           HttpContext.Current.Response.Redirect("PaginaSinAcceso.aspx");
        }
    }  

    private void OnAuthenticate(object sender, EventArgs e)
    {
     Configuration config = WebConfigurationManager.OpenWebConfiguration("/appSettings");

     //Si no está autentificado lo enviamos al login
     if (HttpContext.Current.User == null || !HttpContext.Current.User.Identity.IsAuthenticated)
       HttpContext.Current.Response.Redirect(string.Format("~/Account/LoginNet.aspx?ReturnURL=~{0}", HttpContext.Current.Request.Path));

     //Recuperamos ticket 
     FormsIdentity identity = (FormsIdentity)HttpContext.Current.User.Identity;
     FormsAuthenticationTicket ticket = identity.Ticket;

     IPrincipal principal = new GenericPrincipal(identity, ticket.UserData.Split(';'));

     //Se crea la clase Principal y se asigna al CurrentUser del Contexto
     HttpContext.Current.User = principal;
     Thread.CurrentPrincipal = principal;
         
    }
  }
}
Por último tenemos que registrar el module desde el archivo web.config tal y como vimos en este artículo.

Con esto ya tendríamos las bases para personalizar la seguridad Forms de nuetro site

ASP.NET

, seguramente si os decantáis por este sistema tendréis que ampliar este ejemplo pero espero os pueda servir como punto de partida en algún caso. Recordaros como siempre que podéis seguir

areaTIC

en las redes sociales y enviarnos vuestras valoraciones y/o comentarios. Anímate a participar!


martes, 5 de marzo de 2013

SQL Server: FILL FACTOR

FILL FACTOR

(factor de relleno) es una propiedad de los índices que indica el porcentaje de espacio en cada página (del nivel de hoja) que se rellenará con datos y por tanto qué espacio quedará disponible para que el índice siga creciendo. Por ejemplo, un

fill factor

de 70% indicará que cada página de nivel de hoja tendrá disponible un 30% de espacio para inserciones en el índice.


En

SQL Server

las páginas tienen un tamaño de 8Kb y el

fill factor

es un valor de 0 a 100 (el 0 es igual que el 100, la página se rellena completamente). Mediante las instrucciones CREATE INDEX y ALTER INDEX podemos establecer el

fill factor

para un determinado índice.

Es importante elegir correctamente un

fill factor

adecuado para un índice:

  • Si seleccionamos un

    fill factor

    demasiado bajo (por ejemplo 10%) tendremos gran parte de las páginas vacías, habrá espacio suficiente en el índice para los posibles INSERT / DELETE / UPDATE que le afecten pero serán necesarias un mayor número de páginas que si utilizáramos un

    fill factor

    superior, por tanto, necesitaremos más espacio de almacenamiento y se reducirá el rendimiento de las lecturas.

  • Si seleccionamos un

    fill factor

    demasiado alto (por ejemplo 99%) y hay numerosos INSERT / DELETE / UPDATE que afectan al índice será necesario hacer divisiones de página para proporcionar espacio al índice. Estas divisiones de página consumen muchos recursos y además se produce fragmentación del índice lo que todavía genera más operaciones de E/S. Destacar que cuando una página de índice se divide los elementos se reparten entre ambas páginas al 50%, no se tiene en cuenta el

    fill factor

    del índice. La fragmentación del índice la solucionamos realizando operaciones de reorganización y regeneración de índices.

Entonces, ¿cuál es el

fill factor

más adecuado para un índice? Hay distintas recomendaciones que nos pueden ayudar a elegir el

fill factor

adecuado:

  • Un

    fill factor

    distinto de 100% puede ser adecuado para el rendimiento si los nuevos datos están distribuidos uniformemente en la tabla.

  • Si todos los datos se agregan al final de la tabla (por ejemplo, si el índice está definido sobre una columna identity) aplicaremos un

    fill factor

    con valor 100%, la clave para las filas nuevas del índice siempre aumenta por tanto siempre se insertarán al final, no tiene sentido dejar espacio libre en las páginas del índice.

  • Un índice definido sobre una tabla estática en la que sólo hayan lecturas (no hay INSERT / DELETE / UPDATE) debería tener un

    fill factor

    de 100%.

  • Para índices definidos sobre tablas que tienen pocas modificaciones (INSERT / DELETE / UPDATE) es recomendable un

    fill factor

    del 95%.

  • Para índices definidos sobre tablas con muchas modificaciones (INSERT / DELETE / UPDATE) es recomendable un

    fill factor

    entre el 70% y el 90%.

  • En raras ocasiones encontraremos adecuado un

    fill factor

    por debajo del 70% pero cuidado, podría darse el caso.

Lo anterior son recomendaciones, podemos tomarlas como punto de partida y luego analizar la fragmentación y densidad del índice para ir ajustando el

fill factor

según convenga. La fragmentación y densidad de índices lo dejaré para el artículo porque tiene su cosa, vayamos a ver ahora algunos ejemplos relacionados con el

fill factor

.

Para saber el

fill factor

de los índices existentes en una base de datos ejecutaremos la siguiente consulta:
-- Obtenemos fill factor de los índices de una base de datos
SELECT OBJECT_NAME(OBJECT_ID) AS TableName, NAME AS IndexName, 
       TYPE_DESC AS IndexType, FILL_FACTOR
FROM SYS.INDEXES
Tomemos como ejemplo la base de datos AdventureWorks, modifiquemos el

fill factor

de un índice existente:
-- Modificamos el fill factor al 85%
ALTER INDEX IX_EmployeeDepartmentHistory_ShiftID 
ON HumanResources.EmployeeDepartmentHistory 
REBUILD WITH (FILLFACTOR = 85); 
En relación al índice anterior, podemos crearlo de nuevo con un

fill factor

diferente:
-- Creamos el índice con fill factor del 80%
CREATE INDEX IX_EmployeeDepartmentHistory_ShiftID 
ON HumanResources.EmployeeDepartmentHistory (ShiftID) 
WITH (DROP_EXISTING = ON, FILLFACTOR = 80); 
Algunas consideraciones más a tener en cuenta:

  • Tanto en el comando CREATE INDEX como ALTER INDEX encontramos la opción PAD_INDEX = { ON | OFF }, ¿qué quiere decir? Con PAD_INDEX = OFF las páginas del índice de niveles intermedios se rellenan al completo, con PAD_INDEX_ON se rellenerán con el valor de

    fill factor

    especificado para el índice (por ejemplo, al 80%).

  • Por defecto los índices se crean con

    fill factor

    0 porque ese es el valor que por defecto trae configurado SQL Server. Podemos cambiar el valor por defecto de la siguiente manera:
    -- Obtenemos información sobre las opciones de configuración 
    -- Podemos ver la opción 'fill factor (%)'
    SELECT * FROM sys.configurations
    GO
    
    -- Cambiamos el fill factor por defecto a 95%
    EXEC sys.sp_configure 'show advanced options', '1'
    RECONFIGURE WITH OVERRIDE
    GO
    
    EXEC sys.sp_configure 'fill factor (%)', '95'
    RECONFIGURE WITH OVERRIDE
    GO
    
    EXEC sys.sp_configure 'show advanced options', '0'
    RECONFIGURE WITH OVERRIDE
    GO
    
Y hasta aquí el artículo de hoy, espero que os sirva para mejorar el rendimiento de vuestros índices. Recordad también que dentro de

areaTIC

podéis encontrar otros artículos, no dudéis en consultar nuestro archivo, también podéis seguirnos por RSS o las principales redes sociales (twitter, facebook, linkedin...) 


LECTURAS RELACIONADAS RECOMENDADAS POR AREATIC.NET