[C#] Efectos de AsParallel()

Durante la pasada DotNetConference  y después de la charla sobre Async Best Practices de Lluis Franco vino Rodrigo con una serie de curiosidades de los efectos de AsParallel() sobre una colección. Y de esas curiosidades, nace este post.

El ejemplo que comentamos es sencillo. Se trata de calcular cuántos números primos hay de 1 hasta N. Para ello he usado una función sencilla para calcular si un número es primo o no:


static bool IsPrime(int candidate)
{
	if ((candidate & 1) == 0)
	{
		if (candidate == 2)
		{
			return true;
		}
		else
		{
			return false;
		}
	}
	for (int i = 3; (i * i) <= candidate; i += 2)
	{
		if ((candidate % i) == 0)
		{
			return false;
		}
	}
	return candidate != 1;
}

Y he creado una lista de enteros hasta N:


var list = Enumerable.Range(1, 10000000);

Quedando la consulta sobre LINQ de la siguiente manera:


var onlyPrimes = list.Where(i => IsPrime(i))
		     .Count();

Evidentemente esto tiene un coste. Para pocos elementos es prácticamente despreciable, pero si generamos una cierta cantidad de elementos a partir de 1 millón vemos como este se dispara:

image

Una solución para prevenir este tipo de casos es usar AsParallel(). Este método permite distribuir el contenido de un IEnumerable para que se habilite el procesamiento en paralelo. Es decir, cambiando la sentencia LINQ original por esta obtendremos del tiempo actual de 7,3 segundos a 2,5 para el último caso:

var onlyPrimesParallel = list
	.AsParallel()
	.Where(i => IsPrime(i))
	.Count();

Y juntando los dos tiempos, la gráfica quedaría tal que:

image

¿Qué efectos produce AsParallel?

Para verlo, usaremos una extensión de Visual Studio que se llama Concurrency Visualizer. Es parecido a los ya conocidos reportes de rendimiento, diagnósticos y análisis sólo que está destinado a mostrar cómo se distribuyen los hilos sobre la memoria y CPU de la máquina. Podemos asociarlo al proyecto en ejecución o asignarle cualquier programa que esté en ejecución.

En el primer caso, que es secuencial, esperamos ver que el hilo de la aplicación se ejecuta en mayor medida sobre uno de los núcleos de nuestro sistema:

image

Mientras que si vemos el resultado de la opción en ejecución en paralelo obtenemos lo siguiente:

image

Se puede apreciar fácilmente que hay mucha carga y más procesos que están distribuidos a lo largo de los núcleos.

¿Debemos usar AsParallel?

Bien, antes de responder la pregunta hay que aclarar una serie de puntos. AsParallel no es gratuito. La paralelización no es gratuita. Es costosa. Y Mucho. Para una CPU que no paralaleliza no hay mayor problema, porque entra un proceso y este ocupará el tiempo de la CPU sin importar nada más. Cuando termine devolverá su trabajo y la CPU podrá seguir con otra cosa. Cuando se paraleliza, al coste de las operaciones que se estén haciendo, hay que añadirle el peaje de paralelización. Este peaje es el coste que tiene para el núcleo y el sistema sincronizarse con sus semejantes. Veamos más información del uso de los núcleos en el caso paralelo:

image

El cambio de contexto es el “peaje” que comenté anteriormente. Supone el proceso que se debe llevar para que los núcleos de la CPU se sincronicen para ejecutar la tarea de forma paralela. Esto implica que en cada cambio de contexto, los registros de los procesadores son guardados y cargados, el kernel del sistema operativo se ejecutará, la TLB (Translation Lookaside Buffer) se recargará y las etapas de instrucciones del procesador finalizará. Es decir, se fuerza prácticamente a que ejecute nuevo código pero con registros ya cargados para que continúe por donde lo dejó la otra etapa paralela. Y le podemos añadir por si fuera poco, que puede ser todavía más costoso para el núcleo si la caché no es válida para el hilo actual. El escenario ideal sería que se paralelice en la justa medida para que todos los núcleos puedan reusar la caché que ya tienen.

Aparecen tres métricas en el reporte:

  • Cross Core Context Switches:  Número de veces que un hilo ha cambiado de un núcleo lógico a otro.
  • Total Context Switches: Número de veces que se cambia el contexto (de ejecución a sincronización, etc).
  • Percent of Content Switches: Las dos métricas anteriores divididas (CrossCore / Total Context). Cuando más alto sea el porcentaje, mayor es la actividad del núcleo y del hilo en particular.

Ahora ya podemos responder a la pregunta. Sabemos que es costoso, porque implica un coste asociado por hilo al paralelizar. Así que:

¿Debemos usarlo? Depende. Hay que medir qué aporta y que no aporta. En la segunda gráfica se aprecia que el coste es algo superior al secuencial, pero a medida que crece la lista el coste del paralelo tiene una pendiente con menor inclinación que la secuencial. Otro punto es ver qué hacemos con la paralelización. Si el resultado implica recoger los resultados del proceso, será más caro –como es el ejemplo actual- Si sólo queremos aplicar una operación (cómo hacer una petición HTTP) será más barato. En cualquier caso, hay que medir y probar.

¿Puedo colocar AsParallel donde quiera? Sí, por supuesto. Pero evidentemente eso no significa que funcione como crees que debería funcionar. En la sentencia LINQ actual, AsParallel() está justo antes del WHERE() porque lo que queremos es que distribuya los N elementos de la lista en los núcleos de la CPU y que una vez distribuidos, ejecute el WHERE(). Esto ha sido porque hemos asumido que lo que ejecuta el WHERE() es atómico y costoso. Si por el contrario colocamos AsParallel() después, provocaremos esto:

image

Sí, efectivamente usa todos los núcleos disponibles. También tarda casi más de 3 segundos que la forma secuencial. Lo que hemos provocado aquí es:

  • La ejecución de calcular un número primo es puramente secuencial. Es decir, tenemos una lista de N elementos y para cada uno de ellos vamos a determinar es primo o no. Puramente secuencial.
  • Cuando tengamos todo el resultado, que habrá devuelvo una cantidad X<N de elementos (aquellos que sean primos) aplicamos el AsParallel(). Y la siguiente operación es un COUNT() de forma paralela, por lo que el sistema va a distribuir los X elementos entre los núcleos para poder contarlos y finalmente sumarlos para obtener el resultado final. ¿Es paralelo? Por supuesto, pero de forma incorrecta porque hemos hecho el cálculo de los primos en secuencial y le hemos añadido el sobrecoste de la paralelización para obtener el COUNT. Y además se puede apreciar como el número de cambios de contexto es bastante mayor que la versión paralela correcta, por lo que en lugar de mejorar el rendimiento, lo hemos empeorado con creces respecto a la versión secuencial.

BONUS: DNX

Probando el código de AsParallel() con DNX 4.5.1 (el DNX Core que tengo instalado no soporta AsParallel por el momento) he obtenido un tiempo máximo de 2,2 segundos (0,3 menor que la media –que no máximo-) del ejemplo anterior. Cuanto menos curioso…

Puedes encontrar el código que he usado en GitHub

[Evento] DotNetConference 2016: Un breve y personal resumen

Siguiendo la estela del año pasado, este año hemos tenido una nueva edición de la DotNetConference. Es un evento organizado por Microsoft y orientado para todas las comunidades técnicas y desarrolladores que trabajamos con tecnologías Microsoft. Se volvió a batir la afluencia: 1700 personas asistieron de forma presencial y otras 5000 de forma online siguiendo la retransmisión vía streaming.

Tal vez la primera gran diferencia respecto al año pasado es que sólo ha durado un día. En la pasada edición la duración se extendió a dos días, por lo que hubo más espacio y más tiempo entre las charlas. Este año todas las charlas han estado comprimidas en el mismo día, lo cual ha sido un poco caótico porque había que elegir entre ir a la charla o hacer algo de networking. Sin embargo, hay otra pequeña diferencia que sí hemos tenido este año:

Sí señor, Satya Nadella (CEO de Microsoft) pudo asistir al evento e impartir una keynote. Un gran salto de calidad y potencia al evento, porque no todos pueden presumir de tener como ponentes al CEO de una de las mayores empresas de software del mundo. Aquí se ha demostrado el gran trabajo de organización efectuado por el DX Team de Microsoft Ibérica. Supongo que esta noche podrán dormir tranquilos y descansar, porque desde luego se lo tienen merecido después de todo el esfuerzo que han hecho.

Respecto al contenido del evento, otro año más no he asistido a la primera keynote. Tampoco he podido asistir a todas las charlas que me gustaría. Desgraciadamente muchas coincidían en la misma hora pero en distinta sala, así que la elección no fue fácil.

  • Some dirty, quick and well-known tricks to hack your bad .NET WebApps: Chema Alonso impartió esta divertida y práctica charla sobre pequeñas cosas a tener en cuenta a la hora de desarrollar una aplicación web. Nos demostró, con ejemplos reales y en directo que una mala configuración de servidor (tan simple como dejar los valores por defecto) puede suponer que un atacante que tenga el suficiente tiempo libre nos dé alguna sorpresa. Chema terminó la charla con una frase lapidaria: “Si no revisas los detalles de tu aplicación web no te preocupes, alguien lo hará”
  • Entity Framework Core 1.0: Unai Zorilla nos vuelve a hacer una demostración que es posible hacer una charla picando código y sin necesidad de IntelliSense. Explicó la motivación que hay detrás de Entity Framework 7 Entity Framework Core 1.0, tanto para aplicaciones Full .NET como para DotNet Core. Hizo hincapié en algunas de las nuevas características que incluye EF 1.0 (que ahora mismo está en RC1) muy interesantes y potentes. Aunque en mi opinión, una gran potencia conlleva una gran responsabilidad. Como último apunte, EF Core 1.0 ha sido reescrito desde cero y no soporta todavía toda la funcionalidad que tiene EF6. Así que entre esos motivos y que todavía está en RC1, no se recomienda su uso en sistemas de producción.

  • El arte del logging en el Cloud: Luis Guerrero vuelve a marcar una charla espectacular. Contenido y explicación directa. A través de una aplicación que analiza los problemas que puedan aparecer en diversos sitios webs, emplea AppInsights para analizar y obtener la información de lo que va ocurriendo. Porque como él mismo dijo, está muy bien tener un sistema de logging. Pero es mucho más importante tener la capacidad de explotarlo para poder analizar y saber lo que ocurre. Tener un sistema de logging y no poder leerlo o directamente no leerlo, es lo mismo que no tenerlo.
  • Wearables con C# y .NET: Microsoft Band 2, Apple Watch y Google Wear. Josué Yerai nos explica las diferencias entre Microsoft Band, el Apple Watch y cualquier Android Wear. Algo muy curioso porque todas tienen su propio SDK y aunque parezca sorprendente, la única que dispone de SDK para todas las plataformas es Microsoft Band. A través de un ejemplo mostró cómo funciona realmente las aplicaciones de los wearables y qué podemos hacer con cada uno de ellos.

Mención especial (y no por temas corporativos) me gustaría hacer a mis compañeros de Plain Concepts. Organizaron un concurso a través de un cuestionario de cinco “simples” preguntas de programación en C# que causó furor. No creo que sea por el premio (una Microsoft Band 2 al que acertase las cinco preguntas) sino por el hecho de que es algo que motiva y “pica” para poder investigar y obtener la respuesta correcta. La afluencia al stand de gente rellenando el cuestionario y entregándolo fue constante a lo largo del día, por lo cual considero que fue todo un éxito por la iniciativa de todos los implicados.

Por último me gustaría volver a agradecer a todos la organización, ponentes y patrocinadores la realización del evento. No es sencillo montar un evento de este tipo ni para tanta gente. Y en el fondo, lo importante es que es un evento de comunidad. Una comunidad que está viva y muy activa. Y es algo que en mi opinión, motiva bastante para poder seguir investigando y aprendiendo nuevas tecnologías. Así que: ¡enhorabuena!

[Deploy] Trasformaciones de config (II): App.config

Siguiendo con la temática del artículo anterior en el que se mostró cómo crear configuraciones y como aplicar los valores en el web.config para cada una de ellas, en esta ocasión dedicaré el asunto a App.config

¿Por qué lo visto anteriormente no es válido? Técnicamente es válido, pero el problema es que por defecto los app.config no están pensados para soportar transformaciones y los proyectos en los que se encuentran, tradicionalmente de consola o aplicaciones de escritorio, no tienen en cuenta este detalle.

Si se crea un proyecto de consola o aplicación de escritorio en general se aprecia que el app.config no dispone de versión para Debug y Release, pese a que dichas configuraciones sí existen en el proyecto o solución. Por lo tanto, lo primero que se debe hacer es habilitar tantos app.config como configuraciones se disponen.

Como resumen breve, en el post anterior se comenta lo siguiente:

  • Por un lado, las distintas versiones de web.config son en realidad desde el punto de vista del proyecto ficheros dependientes de otro.
  • Por otro lado, para poder habilitar las transformaciones es necesario añadir el import correspondiente.

Crear los app.config

Respecto al primer punto la principal diferencia es que debemos crear los ficheros de forma manual, puesto que no tenemos menú disponible para ello. Así pues, si se dispone de la misma configuración que el artículo anterior de cuatro entornos (dev, qa, pre y pro) se creará en primer lugar los app.config equivalentes:

Además cada fichero de configuración se ha creado con una estructura básica por defecto que deberemos preparar. Mientras que la parte original permanece así:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

</configuration>

Se debe añadir el namespace de transformaciones:

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

</configuration>

Por lo tanto la estructura de ficheros que queda en el proyecto sería algo muy parecido a esto:

image

Para que los ficheros pemanezcan jerarquizados en base al App.config original, se debe modificar el csproj indicando que el resto de ficheros que hemos creado por entorno tienen son dependientes del App.config “padre”:

<ItemGroup>

  <None Include="App.config" />

  <None Include="App.Debug.config">

    <DependentUpon>App.config</DependentUpon>

  </None>

  <None Include="App.PRE.config">

    <DependentUpon>App.config</DependentUpon>

  </None>

  <None Include="App.QA.config">

    <DependentUpon>App.config</DependentUpon>

  </None>

  <None Include="App.Release.config">

    <DependentUpon>App.config</DependentUpon>

  </None>

</ItemGroup>

De este modo ya se dispone de un app.config jerárquico al igual que ocurre en un proyecto web:

image

Trasformación de app.config

Ahora se debe importar la capacidad de transformar el XML. Fácilmente se puede lograr añadiendo la siguiente línea en el csproj:

<Import Project="$(MSBuildExtensionsPath)MicrosoftVisualStudiov12.0WebMicrosoft.Web.Publishing.targets" />

Ahora podemos modificar el web.config y añadir algo en función del entorno. A modo de ejemplo mantengo el del artículo anterior, por lo que el web.config original quedaría de la siguiente forma:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <appSettings>

    <add key="environmentName" value="none"/>

  </appSettings>

    <startup> 

        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />

    </startup>

</configuration>

Y el de QA así:

<?xml version="1.0" encoding="utf-8" ?>

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

  <appSettings>

    <add key="environmentName" value="QA"

         xdt:Transform="Replace"

         xdt:Locator="Match(key)"/>

  </appSettings>

</configuration>

Si compilamos en QA, se aprecia que no ha aplicado ningún tipo de transfomación y mantiene el web.config original. Aquí tenemos dos problemas:

  • El proyecto de consola no entiende de transformaciones, somos nosotros quienes las estamos introduciendo casi “a calzador”.
  • No disponemos de publicación condicionada. En un proyecto web se puede establecer multitud de parámetros que se tienen en cuenta a la hora de publicar, entro ellos la configuración del entorno. Aquí directamente se ofrece la posiblidad de publicar con la única opción de indicar la ruta donde se copiará el resultado de la compilación que en esta opción será siempre Release.

Por lo tanto, lo que se debe hacer es habilitar que tenga en cuenta en la compilación el resto de entornos personalizados que se han ido creando. Para ello se necesita añadir al csproj la siguiente opción post-compilación:

<Target Name="AfterBuild">

  <TransformXml 

    Source="@(AppConfigWithTargetPath)" 

    Transform="App.$(ConfigurationName).config" 

    Destination="@(AppConfigWithTargetPath->'$(OutDir)%(TargetPath)')" />

</Target>

Se añade TransformXML gracias al soporte del Import de Web.Transformation y se transformará todo fichero App.{Entorno}.config. La variable $(ConfigurationName) es una macro predefinida de msbuild que indica cuál es el nombre de la configuración que actualmente está seleccionada. Como se ha seguido el patrón de generar la configuración por entorno (app.debug.config, app.qa.config, etc) podrá encontrar el fichero y de este modo aplicará la transformación. Es decir, gracias a esta tarea de post-compilación se comprobará el efecto de la transformación al compilar el proyecto independientemente o no de la publicación del mismo, justo al contrario que en un proyecto web. Finalmente el app.config para QA quedaría del siguiente modo:

<?xml version="1.0" encoding="utf-8"?>

<configuration>

  <appSettings>

    <add key="environmentName" value="QA"/>

  </appSettings>

    <startup> 

        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />

    </startup>

</configuration>

 

Opciones de terceros

Existen estas opciones en lugar de aplicar el DIY:

  • Configuration Transform: Es una extensión de VS(2010,2012,2013 por ahora) que añade la funcionalidad que aquí se ha aplicado de forma manual.
  • SlowCheetah: No he llegado a usarlo pero parece ser que está descontinuado. Hace un año o dos era una buena solución para la gestión de XML en configuraciones.

Fuente:

Basado en esta respuesta de Stackoverflow y experiencias varias

[Deploy] Transformaciones de .config (I): Web.config

Cuando se desarrolla una aplicación sea del tipo que sea, siempre se incluye un fichero .config donde aparecen datos de la configuración (settings, cadenas de conexión, configuración de dlls, versiones específicas de dlls, etc). Si estamos en una aplicación web tendremos un fichero web.config y si estamos en otro tipo de aplicación se llamará app.config.

Creando entornos

Normalmente en web se trabajan con múltiples entornos al desplegar. Podemos disponer de un entorno de desarrollo, de un entorno de QA/test, de un entorno de preproducción y finalmente de un entorno de producción. Normalmente a cada entorno se corresponde una configuración de despliegue distinta, porque tendrán settings o cadenas de conexión distintas.

Cuando creamos un proyecto web, automáticamente nos genera un web.config. Sin embargo podemos apreciar que nos genera dos ficheros: uno para Debug y otro para Release:

image

Lo cual en el csproj se traduce de la siguiene forma:

<Content Include="Web.Debug.config">

  <DependentUpon>Web.config</DependentUpon>

</Content>

<Content Include="Web.Release.config">

  <DependentUpon>Web.config</DependentUpon>

</Content>

Y para generar esos ficheros, la plantilla de VS se basa en la configuración de la solución que exista. Por defecto se crean dos configuraciones: debug y release para cada uno de los proyectos integrados en la solución:

image

Lo cual nos permite crear rápidamente tantas configuraciones como necesitemos. Además, podemos crearlas basándonas en otras ya existentes:

image

Y una vez creadas, nos situamos encima de Web.Config y ya Visual Studio nos sugiere que podemos añadir nuevas configuraciones:

image

image

A partir de este momento, cuando se publique la aplicación se aplicará la transformación de web.config a la configuración seleccionada. Es decir, los efectos del web.config sólo tienen validez una vez se ha desplegado la aplicación, por lo que no es posible probarlo si desde el propio Visual Studio cambiamos la configuración al ejecutar/probar.

Transformaciones

Si disponemos de un web.config base,compilamos y desplegamos con una solución determinada (Debug, Release o cualquier otra que se haya creado de forma previa) este se transformará. ¿Cómo? Si se presta atención al csproject del proyecto, se importa una extensión de msbuild:

<Import

Project="$(MSBuildExtensionsPath32)MicrosoftVisualStudiov10.0WebApplicationsMicrosoft.WebApplication.targets" 

Condition="false" />

 

Esta extensión permite que después de la compilación, se aplique el web.config original y después se apliquen las transformaciones que se hubieran definido en la configuración específica de cada entorno.

Pero ¿qué es una transformación? Es una alteración del web.config base para insertar, eliminar o modificar parte de la estructura del XML. Véase en detalle una configuración de Release por ejemplo:

<configuration xmlns:xdt="http://schemas.microsoft.com/XML-Document-Transform">

  <system.web>

    <compilation xdt:Transform="RemoveAttributes(debug)" />

  </system.web>

</configuration>

Véase el uso del namespace de XML-Document-Transform. Este habilitará las instrucciones necesarias para las trasformaciones. Las más comunes suelen ser las relativas a la substitución de un atributo XML, añadir un nuevo nodo o reemplazarlo por otro. Los atributos que se usarán serán los siguientes:

  • Transform: Indica la naturaleza de la operación: Replace, Insert, InsertIfNotExists, etc.
  • Locator: Si necesitamos localizar el nodo para una sustitución, se indicará aquí el nombre del atributo que se usará a tal efecto.

Por ejemplo, si se tiene una sección de appsettings en el web.config base tal que:

<appSettings>

  <add key="environmentName" value="none" />

</appSettings>

En un hipotético web.QA.config sería del siguiente modo:

<appSettings>

  <add key="environmentName" value="QA" 

    xdt:Transform="Replace" 

    xdt:Locator="Match(key)" />

</appSettings>

En el cual indicamos en Transform que se desea aplicar un Replace en aquel nodo cuyo atributo Key sea “environmentName”. Así al publicar la aplicación en con la configuración de QA, se aplicarará esa transformación.

También se puede reemplazar un nodo completo, en cuyo caso no se indicaría el atributo locator. Mientras que en un web.config base no aparece este nodo, podemos insertarlo en cuaquier transformación:

<system.webServer >

    <applicationInitialization xdt:Transform="Insert" >

      <add initializationPage="/Static/QAAdvice"/>

    </applicationInitialization>

</system.webServer>

O incluso insertar un nodo completo:

...

<system.diagnostics xdt:Transform="Insert">

  <trace>

    <listeners>

      <add type="Microsoft.WindowsAzure.Diagnostics.DiagnosticMonitorTraceListener, Microsoft.WindowsAzure.Diagnostics, Version=2.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" name="AzureDiagnostics">

        <filter type="System.Diagnostics.EventTypeFilter" initializeData="Warning"/>

      </add>

    </listeners>

  </trace>

</system.diagnostics>

configuration>

De este modo se puede configurar cada entorno y configuración y que se apliquen los cambios correspondientes.

Si quiere conocer más a fondo las transformaciones de XML para proyectos web, consulte este enlace.

[Evento] Freakend 2015

El fin de semana pasado tuvo lugar el Freakend 2015. Es una reunión en una casa rural en la que nos juntamos diferentes personas pero con un mismo objetivo: nuestra pasión por los videojuegos.

FREAKEND_02_200

48 “frikis” de los videojuegos entre los que contaban desarrolladores, artistas, productores y aficionados como un servidor entre los que su dedicación profesional no son los videojuegos en estos momentos. La idea como siempre es organizar charlas en base a la experiencia de cada uno. Todo el fin de semana estuvo repleto de charlas muy interesantes y de diversos ámbitos: desde la experiencia de hacer un kickstarter hasta cómo programar juegos con C# y GameBoy. Tanto era el interés que cada uno de nosotros venía de una ciudad distinta o incluso de algún país extranjero para poder asistir.

Tuvimos con la presencia de lo que para mí son grandes leyendas del desarrollo de videojuegos en España, como José Rauly de Mercury Stream o Unai Landa (Digital Legends y ex-Pyro), Sergi Vargas, Jesús Martínez y una lista de enorme calidad en la que me demostró de nuevo lo mucho, muchísimo que tengo que aprender todavía. También se apuntó a la fiesta David Bonilla y nos contó de primera mano su experiencia con Otogami, el famoso buscador de juegos online que te encuentra siempre el mejor precio.

¿Lo mejor del evento? Sin duda alguna, la gente y el hecho de poder compartir experiencias. Si hay algo que adore de este tipo de eventos es que me permite salir de la burbuja de mi trabajo diario y conocer profesionales de otras tecnologías y ámbitos que nos permiten intercambiar conocimientos y opiniones. Se nota y se percibe cuando hay comunidad, pasión y ganas por hacer las cosas bien hechas. Y pese a ser muchos de distintos ámbitos, tuve esa fantástica sensación. Después de cada charla hubo numerosos debates comentando aspectos de la misma y relacionados, con lo que se pudieron compartir numerosas opiniones muy valiosas. También como ocurre en estos casos, al acabar las charlas del día nos juntamos varios en corillos comentando cualquier cosa: desde el estado de la industria, aspectos profesionales, técnicos o directamente qué juegos nos han gustado más.

Como punto original, tuvimos a los GameYourHear que además de asistir al evento nos deleitaron a las 3 de la mañana con unas cuantas canciones en riguroso directo de videojuegos.

WP_20150307_02_50_29_Pro

Y como siempre estuvimos bien alimentados en base a lo que necesita el cuerpo:

WP_20150307_21_57_49_ProWP_20150306_20_42_00_Pro

Por último (y no menos importante) quiero agradecer a Diego y Jorge todo su esfuerzo y empeño para lograr coordinar tanta gente para un evento que ha salido redondo. Sé que han terminado muy cansados y agotados, con toda la razón del mundo. Pero no me cansaré de repetirlo: GRACIAS por hacer cosas como estas.

P.D: Como suele ser habitual, tuvimos algún invitado especial que no se perdió el evento:

WP_20150308_14_32_18_Pro

[Evento] DotNetConference 2015

El pasado fin de semana tuvo lugar en la universidad de Alcalá de Henares la DotNet Spain Conference 2015. Fue un evento espectacular y que se echaba en falta.

Desgraciadamente no pude asistir a la keynote ni a las primeras charlas, así que voy a comentar sobre lo que sí tuve ocasión. También desgraciadamente no me pude desdoblar para asistir a más de una charla o taller a la vez, porque había varías a la misma hora y en distintos tracks muy interesantes. Vaya por delante mi agradecimiento a todas las personas implicadas en la organización de tal magno evento en el que hubo 1000 asistentes y otros tantos virtuales siguiendo las charlas y talleres de forma online. Se echaba en falta un evento donde la comunidad .NET podamos juntarnos y compartir nuestras experiencias, ya que si por alguien lo dudada existimos Sonrisa

Tengo mi aplicación en el cloud y no escala: ¿Qué hago? En esta charla Quique Martínez nos cuenta experiencias reales de clientes que han querido llevar sus aplicaciones on-premise a la nube y los problemas que han tenido. Muy útil para conocer las implicaciones de una decisión que no se debe tomar a la ligera.

Complex Event Processing con Event Store: Marçal Serrate nos cuenta cómo modelar y usar un EventStore de una forma muy curiosa. Mostró un ejemplo usando la api de GitHub, en el que extrajo estadísticas de los lenguajes más usados en función de los commits. Después usó Event Store para analizar los comentarios y mostrar estadísticas de en qué lenguajes se incluyen más palabras mal sonantes. Muy curioso.

Sube tu Universal App al Cloud: Aquí asistí a medias, pero Adrían Fernández nos cuenta cómo desarrollar una universal app de ingredientes de hamburguesas y subir la api correspondiente a Azure.

MongoDB en Azure para programadores de .NET: Aquí Francesc Jaumot nos cuenta una introducción sobre qué es Mongo Db y cómo integrarlo dentro de una aplicación .NET.

Async best practices: Lluis Franco y Alex Casquete explican en profundidad la evolución de asincronía en todas las versiones del framework: desde eventos y delegados hasta el async/await. Estuvo bastante bien porque contaron algunos temas sobre la parte interna de la gestión de tareas dentro del framework.

Code Smells: Fernando Escolar nos ilustra sobre aquellos fragmentos de código que nos persiguen durante la noche y lo más profundos de los sueños cual orcos buscando oro y plata. Código que todos hemos escrito alguna vez y no está bien y cómo corregirlo haciendo que quede más elegante y funcional.

Por qué deberías plantearte F# para tu próximo proyecto: Una charla introductoria sobre F# y lenguajes funcionales que impartió Alex Casquete. Promete, porque ya hay una comunidad creciente de usuarios en F# en Madrid y Barcelona que en un futuro no muy lejano nos contarán cosas muy molonas…

Let’s Fight !!! Arduino vs Netduino vs .Net Gadgeteer vs Galileo: La verdad es que asistir a un evento de este calibre y no asistir a una charla de Bruno debería ser motivo de castigo. Nos explicó las múltiples alternativas de IoT, tanto de .NET como de C y las comparó en base a su experiencia. Muy interesante porque es un mundo que apasiona a la vez que asusta.

Gestión masiva de datos en la era IoT: Cuando empezamos a capturar datos con nuestros sensores de IoT, ¿qué hacemos con ello? Luis Guerrero nos propone usar Azure Stream Analytics con una charla interactiva en la que mediante Twitter fuimos viendo el resultado y análisis de los Twitts que cada uno de nosotros mandó.

Algunas que me hubiera gustado ir son CloudFirst, Visual Studio Online, Effective C#, el taller de Machine Learning, Release Management, Galileo y el IoT, Cloud Hardcore Debugging… Lo interesante de este tipo de eventos es que permite explorar áreas y conocimientos que en tu día a día es prácticamente imposible que alcances. Ya de por sí es interesante conocer áreas nuevas, pero todavía más es encontrarme con gente de la comunidad, antiguos y nuevos MSP’s e incluso algún amigo y compañero de clase en la universidad.

Por último, de nuevo me gustaría agradecer a TODA la organización su esfuerzo y dedicación para lograr un evento muy interesante y que me ha traído a la memoria aquellos eventos grandes de hace años donde todos nos reuníamos. Espero que el año que viene podamos disfrutar de una nueva edición. Hasta entonces, simplemente ¡mil gracias!

[Reflexión] Rover Curiosity y pruebas

Hace ya varios años que tomé la decisión de descartar cualquier temática no técnica para los blogs y dejar a un lado las ideas o pensamientos que pudiera tener, inclusive aquellos relacionados con las propias materias técnicas. Sin embargo, hay ocasiones que a veces la parte técnica no es mostrar qué fragmento de código, qué framework o qué tecnología he usado para resolver un problema. Una idea, un matiz o una sugerencia pueden dar con la pista y abrir un nuevo mundo de posibilidades.

Ayer en La 2 (sí, veo La 2, ¿qué pasa?) emitieron un documental sobre la construcción del Rover Curiosity que desde el punto de vista ingenieril es muy pero que muy interesante:

Considero que la formación recibida como ingeniero informático ha sido satisfactoria; aunque como todo en la vida, puede ser mejorable en algún campo más que en otro. Recuerdo todavía casi como si fuera ayer, las clases en las que nos enseñaban metodologías de gestión de proyecto y un poquito de tareas más o menos relacionadas pero sin entrar en profundizar (algo de pruebas, algo de gestión de personal, algo de estimación, algo de arquitectura, etc) lo cual me debería llevar a tener una cierta visión del Mundo Real para poder sobrevivir en él.

Sin embargo, tengo muchísimas lagunas, de las que poco a poco (bien aprendiendo despacito o generalmente mediante palos) voy consiguiendo rellenar. Hoy voy a hablar de pruebas. En el documental podrás ver algunas de las pruebas a las que someten al Rover Curiosity y a algunos de sus elementos. En la carrera me enseñaron poco de pruebas y es algo que debería ser desde mi punto de vista algo fundamental en cualquier ingeniería.

P & P: Hacer Pruebas es de Pobres

Probar debería ser lo más normal del mundo. Metodologías como TDD y BDD se centran precisamente en probar lo que hace desde distintos puntos de vista. ¿Quién no ha oído hablar de TDD? ¿Quién no ha visto una charla/evento sobre TDD? ¿Quién aplica TDD? Ah amigo…

La ingeniería del software es una (¿ingeniería?) muy reciente. Los fundamentos matemáticos de la computación moderna (sin desmerecer a pioneros como Ada, Pascal, Baggage e incluso el misterioso mecanismo de Anticitera) nace a partir de 1920-30, de la mano de científicos como Von Neumann o Turing. A partir de ahí, el ritmo de desarrollo de la ciencia de la computación va cobrando un camino vertiginoso hasta nuestros días en los cuales la tecnología avanza a un ritmo que difícilmente podemos seguir. Nos hemos acostumbrado a servicios como Facebook, Youtube, Gmail/Hotmail, la nube… todo el mundo los conoce y parece que siempre han estado ahí. Parece, porque realmente alguno ni siquiera tiene 10 años.

Este rápido avance provoca que las disciplinas tengan que adaptarse casi a paso cambiado. Otras ingenierías como la civil, industrial o química llevan décadas, siglos e incluso milenios de desarrollo con lo cual sus procesos están asentados, depurados y aceptados. Nosotros todavía estamos discutiendo qué metodología es mejor o más adecuada porque, sencillamente es un mundo cambiante al cual tenemos que adaptarnos y proponer constantemente alternativas y soluciones para poder sobrevivir.

Todo lo que usamos a diario ha sido sometido a multitud de pruebas. Desde nuestra casa, el coche (con sus famosas pruebas de airbag), carreteras, puentes… Hasta el teclado sobre el que escribo este texto ha sido sometido a durísimas pruebas para estimar cuánto tiempo debe durar y proporcionar un funcionamiento correcto bajo una serie de condiciones.

¿Ocurre lo mismo con el software? Desgraciadamente, no. Las excusas son variopintas y todos, YO el primero, las hemos pronunciado alguna vez:

  • No tengo tiempo para hacer pruebas.
  • Hacer pruebas no sirve para nada.
  • No sé cómo probar esto.
  • Paso de hacer pruebas; esto funciona de lujo.
  • Hay pruebas, pero no funcionan.
  • etc…

http://thecodinglove.com/post/80285273617/when-i-dont-have-time-for-writing-tests

Justificadas o injustificadas, son excusas. Y son excusas porque hay solución a cada una de ellas:

  • “No hay tiempo para hacer pruebas” : El 95% de los clientes van a preferir una entrega que les funcione en un alto porcentaje de la funcionalidad contratada antes que una bomba nuclear inestable en sus manos.
  • “No sirve para nada” : Respirar tampoco, fíjate.
  • “No sé como probar esto”: A esta la respondo más abajo.
  • “Paso de hacer pruebas; esto funciona de lujo” : Explotará. Y lo sabes. ¿El motivo? Salvo sorpresa, somos humanos, no máquinas autómatas y cometemos errores. Es normal, cotidiano y predecible. Pero podemos intentar que sea menos cotidiano cada vez.
  • “Hay pruebas, pero no funcionan” : Simple. Si no sirven, se quitan. Y si no, se adaptan. Respondo más abajo también.

Podemos agrupar las excusas en tres tipos:

  • Timing: No se puede dedicar tiempo pese al riesgo que conlleva. Y si conocemos y aceptamos los riesgos, adelante.
  • Pereza: Poco podemos hacer aquí…
  • Conocimientos: Esta es la buena y la más importante.

TDD es una metodología con una curva de aprendizaje inicial muy dura, porque exige un cambio de paradigma mental a algo que no estamos acostumbrados. Normalmente estamos (mal) acostumbrados a programar, arrancar y probar. Si falla, vemos qué falla y vamos a corregirlo hasta que quede listo. Sin embargo no hace falta aplicar TDD/BDD/{ponga usted su favorita} para hacer pruebas. ¿Cuánto tiempo se pierde en parar, cambiar y arrancar? ¿Cuántos pasos hay que dar para volver a ejecutar la prueba? ¡Automaticemos todo esto en la medida de posible!

Probar antes ejecutar la aplicación te puede ayudar con las siguientes capacidades:

  • Adquirir un mayor conocimiento de lo que estás haciendo.
  • Te permite centrarte en una sola cosa y descartar el resto de tareas relacionadas.
  • Te proporciona una visión muy clara de la funcionalidad.
  • Averiguar, probar y acotar comportamientos que no estaban contemplados en un principio.
  • Mejorar el código descubriendo las dependencias, acoplamientos, etc que estaban ocultos.
  • Descubrir funciones o métodos que deberían/no deberían existir
  • etc…

Precisamente una de las bazas de hacer pruebas es el refactor una vez se ha probado para que cada vez el código sea más claro, limpio y conciso respetando la funcionalidad. Y bueno, eso es simplemente (simplificando mucho, claro) teniendo una prueba que represente la validación de un requisito. Podemos cambiar el código, o lo que haga pero sabemos que el test va a recibir una entrada X y devolverá una salida Y. Si eso no ocurre, es que algo hemos roto. Se trata de seguridad. De tener la seguridad empírica, con el paso del tiempo, que el desarrollo y su avance no rompe nada de lo que funcionaba anteriormente.

Be water, my friend

Nadie nace sabiendo y parece que lo olvidamos demasiado a menudo. Reconocer la ignorancia de algo es la primera piedra de conocimiento de ese algo, porque es el momento en que nos planteamos en el subconsciente una necesidad de aprendizaje que anteriormente no se nos presentó.

Probar es algo que supone esfuerzo, especialmente al principio. Y al final también. Las primeras pruebas son feas, mal hechas, desorganizadas, acopladas… y poco a poco, a medida que se van haciendo se mejora. Porque el único método de aprender a hacer pruebas es haciéndolas. Podrás leer muchos libros, consultar o tener mucha referencia a recursos, pero hasta que no te sientas frente al lienzo en blanco no aprenderás a hacer pruebas. Y con la experiencia y la multitud de recursos disponibles (libros, github, blogs) se irá refinando el proceso cada vez.

En un campo en que la tecnología cambia cada 6-18 meses y los requisitos también, no nos queda otra que adaptarnos y cambiar continuamente. Si cambian los requisitos, cambian los tests que deben validarlos. Es una exigencia continua de un flujo continuo…

Para la mayoría de proyectos en los que he trabajado en mi vida profesional, me tomo los despliegues y entregas con demasiado estrés. Intento que todo lo que pueda hacer en función de mis conocimientos esté bien: build etiquetada, todos los tests de todos los tipos pasando, despliegue controlado por si hay que hacer rollback en cualquier momento, bakcups de todo… Son momentos críticos para cualquier sistema porque implica que durante ese tiempo, lo que sea que se esté desplegando no está disponible y hay que asegurar que el despliegue no rompa nada de la versión antigua. Porque en última instancia, podemos hacer rollback y estaremos en una situación de estrés por solventar la incidencia en el menor tiempo posible… pero al menos efectivamente, se puede solucionar.

A veces pienso en la cantidad de pruebas que hacen los ingenieros de NASA, ESA, EADS como el del Rover Curiosity y la presión que deben sentir (proyectos de 2.000 millones de € que dependen de un cable de 8€, por ejemplo) para que todas las pruebas y toda la casuística posible quede demostrada y ejecutada, intentando no dejar nada al azar. Porque una vez el robot ha sido lanzado y está en Marte, no hay vuelta atrás ni solución si ocurre algún problema*. Y eso, como ingeniero no hace más que producirme la más absoluta admiración.

Hay casos en los que por mucho que se sigan las mejores prácticas, por mucho que queramos habrá una parte en la que no tenemos control sobre lo que ocurre. Son cosas inevitables. Pero todo aquello que podamos hacer a nivel de pruebas para garantizar la estabilidad, seguridad y calidad de nuestro trabajo, hagámoslo. Afortunadamente disponemos de la tecnología para que en la mayoría de casos podamos replicar los entornos de producción/despliegue de forma bastante fidedigna, lo que permitiría reducir el riesgo de un despliegue. Este tipo de capacidades no las tiene la industria aeroespacial ni muchas otras, por lo que deberíamos usar las herramientas disponibles para minimizar riesgos y asegurarnos los menores problemas posibles. Porque, a fin de cuentas es una inversión a largo plazo que siempre sale muy, pero que muy rentable.

* Algunos gazapos de la industria aeroespacial derivados de NO hacer las pruebas suficientes:

  • Ariane 5: No se probó el sistema de cálculo de la aceleración hasta el día del lanzamiento. Este sistema venía derivado del Ariane 4 y funcionaba perfectamente, pero no se probó sobre un Ariane 5. Resultado: el piloto automático no interpretó los datos correctamente ya que se produjo una excepción de punto flotante, obligando a los ingenieros de control a autodestruir el cohete ya que el desvío de trayectoria podría haber provocado una auténtica catástrofe.
  • Mars Climate Orbiter: Parte del software de tierra hablaba en millas mientras que otra parte en metros. Resultado: se enviaron datos incorrectos a la nave, no ajustó el ángulo y distancia correcta para situarse en la órbita de Marte. Quedó carbonizada al tomar contacto con la atmósfera marciana.

[ASP.NET Identity] I: Introducción

La irrupción de ASP.NET ha supuesto una ruptura y un giro hacia una nueva modalidad de evolución del producto. Disponemos del código fuente de ASP (al menos en su totalidad) y un ciclo de versiones mucho más dinámico y no anclada a una versión del framework dedicado. En esta serie de artículos voy a dedicarlo a analizar la parte de Identity puesto que supone grandes cambios respecto a las versiones anteriores de Membership.

Como novedades más destacadas tenemos:

  • Aplicable a todo ASP.NET (Webforms, MVC, WebApi, SignalR, etc)
  • Mucho menos acoplado
  • Por lo tanto, más fácil de testear.
  • Soporte de roles, de claims, oauth, WAAD,etc.
  • OWIN
  • Basado en NuGet

Todo esto ha supuesto básicamente romper y hacer de nuevo la parte de Membership, quedando como resultado este diagrama de dependencias:

A través de este esquema vemos un par de cosas básicas:

  • La implementación de Identity está basada en la implementación de Microsoft.OWIN.
  • Para dar soporte a toda la generación, conexión y gestión de datos hace uso de EntityFramework.

Estas dos últimas características unidas a las anteriores suponen una ruptura del paradigma respecto a Membership. Mientras que antes teníamos que “colocar con calzador” nuestras entidades de usuario a Membership para poder trabajar con ella, ahora podemos usar nuestras entidades y poder trabajarlas sin tener que estar tan acopladas a un sistema de autenticación u otro.

Es decir, mientras antes teníamos que usar WebSecurity para todo ahora tenemos un UserManager que dispone de toda la funcionalidad. El concepto de manager acude a nosotros como la parte encargada de gestionar la funcionalidad completa y nos permitirá hacer todo lo posible con el usuario (login, creación, borrado, asignar roles, claims, obtener datos, etc). Para ello hará uso de una UserStore que será quien por debajo implemente toda la funcionalidad necesaria. Estamos separando el manager que usaremos para la gestión de quién se encarga de ejecutar las tareas:

   1: public class UserManager<TUser> : IDisposable where TUser : IUser

   2: {

   3:        public UserManager(IUserStore<TUser> store);

   4:        ...

   5: }

Aquí vemos que nuestro Manager depende de un TUser que implementa la interfaz IUser. Vamos por partes…

IUser

Nuestro TUser será un usuario. Y este usuario no es mas que cómo define Identity la entidad de usuario, basada únicamente en una clave o propiedad única e identificativa y un nombre de usuario. Tenemos dos posibles implementaciones:

  • La primera, la que incluye por defecto que es IUser donde la clave es un string.
  • O podemos extender IUser con una clave del tipo que queramos:
   1: public class MyUser : IUser<int>

   2:     {

   3:         public int Id { get; set; }

   4:         public string UserName { get; set; }

   5:     }

Podemos ir completando nuestro usuario con otra serie de interfaces, como por ejemplo IdentityUserLogin, IdentityUserRole, IdentityUserClaim… todas aquellas que sean necesarias para poder ir construyendo un usuario y una identidad hecha a medida para nuestra aplicación:

image

IUserStore

Lo siguiente que nos pide el Manager es un UserStore. Este será el encargado de proporcionar la funcionalidad que puede usar el manager. Y si vemos, el UserStore se basa en cumplir una serie de interfaces que son Stores que suministran funcionalidad. La más básica de todas es una IUserStore:

   1: public interface IUserStore<TUser, in TKey> : IDisposable where TUser : class, IUser<TKey>

   2:    {

   3:        Task CreateAsync(TUser user);

   4:        Task DeleteAsync(TUser user);

   5:        Task<TUser> FindByIdAsync(TKey userId);

   6:        Task<TUser> FindByNameAsync(string userName);

   7:        Task UpdateAsync(TUser user);

   8:    }

Al igual que el caso anterior, podemos optar por usar la UserStore suministrada por defecto o usar una nuestra. Y del mismo modo que en el caso anterior, vamos añadiendo interfaces a nuestra UserStore para adoptar la funcionalidad deseada:

   1: public class UserStore<TUser> : UserStore<TUser, IdentityRole, string, IdentityUserLogin, IdentityUserRole, IdentityUserClaim>,

   2:  IUserStore<TUser>, IUserStore<TUser, string>, 

   3: IDisposable where TUser : IdentityUser

   4: {

   5: ...

   6: }

image

Así pues, tenemos el siguiente esquema final:

image

Donde primero, definimos nuestra entidad de dominio (por ejemplo, MyUser). Este será usado por una Store (por ahora la UserStore) y esta Store, por el UserManager.

¿Qué ventajas aporta este modelo frente al antiguo? Evidentemente, todo mucho más desacoplado y testeable. Tenemos un control absoluto de las entidades y la funcionalidad que queremos dar a nuestra identidad.

[C#] ¿Cómo funcionan los atributos?

Siguiendo en la línea de mis últimos artículos en los que desgrano un poco el framework de .NET para ver qué hace por dentro, ahora le toca el turno a los atributos.

¿Qué es un atributo?

Un atributo no es más que una clase que sirve para decorar un tipo. Esta decoración puede ser de tipo informativa o funcional. Por ejemplo, podemos decorar una propiedad con el atributo Description para indicar más información a nivel de metadatos y los que trabajamos en ASP.MVC tenemos el archiconocido atributo Authorize que indica si el usuario está autenticado o no para permitir entrar en la acción seleccionada o redirigirlo a una página de error.

¿Cómo crear un atributo?

Muy sencillo, heredamos de Attribute (o si partimos de otro atributo ya creado) y sobreescribimos los métodos que queramos:

   1: public class MyAttribute : Attribute

   2:         {

   3:             public override bool IsDefaultAttribute()

   4:             {

   5:                 return base.IsDefaultAttribute();

   6:             }

   7:  

   8:             public override bool Match(object obj)

   9:             {

  10:                 return base.Match(obj);

  11:             }

  12:         }

Como lo que nos interesa es reusar uno que ya existe, vamos a heredar de Description para añadirle un campo más:

   1: [AttributeUsage(System.AttributeTargets.Property,

   2:                    AllowMultiple = false,

   3:                    Inherited = false)]

   4:         public class CustomDescriptionAttribute : DescriptionAttribute

   5:         {

   6:             private readonly string shortDescription;

   7:  

   8:             public CustomDescriptionAttribute(string shortDescription, string longDescription)

   9:                 : base(longDescription)

  10:             {

  11:                 this.shortDescription = shortDescription;

  12:             }

  13:         }

Con AttributeUsage podemos definir cómo queremos que se comporte ese atributo y para qué queremos que sea: para cualquier tipo de objeto, sólo para clases, métodos, propiedades, etc.:

  • AttributeTarget: Indica cuál es el destino del atributo.
  • AllowMultiple: Indica si para un mismo tipo, podemos colocar el atributo más de una vez.
  • Inherited: Indica si este atributo se heredará en las clases que hereden del atributo actual.

Una vez lo tenemos creado, lo asociamos a lo que queramos o hayamos definido previamente. Sé que el ejemplo no es el más adecuado, pero me gustaría usarlo sólo para ver cómo funciona:

   1: public class Person

   2:        {

   3:            [CustomDescription("First name", 

   4:                "A given name or the name that occurs first in a given name.")]

   5:            public string Name { get; set; }

   6:        }

Con este atributo podemos decorar una propiedad para indicarle una descripción corta y una larga. Esto podría ser útil si queremos exportar esta entidad a un fichero o un documento donde muestre una información más detallada del campo que se está mostrando.

¿Y qué hace por dentro?

Bien, vamos por partes. Evidentemente aquí no hay magia, si ponemos un breakpoint o una salida por consola vemos que el atributo no hace nada. Pero antes de eso vamos a ver que el código que ha generado en CIL:

   1: .class auto ansi nested public beforefieldinit CustomDescriptionAttribute

   2:         extends [System]System.ComponentModel.DescriptionAttribute

Primero vemos que muestra una herencia de una clase normal y que no hay nada del otro mundo. Un objeto cualquiera, vamos. Sigamos:

   1: .custom instance void [mscorlib]System.AttributeUsageAttribute::.ctor(valuetype [mscorlib]System.AttributeTargets) = ( 01 00 80 00 00 00 02 00 54 02 0D 41 6C 6C 6F 77   // ........T..Allow

   2:                                                                                                                            4D 75 6C 74 69 70 6C 65 00 54 02 09 49 6E 68 65   // Multiple.T..Inhe

   3:                                                                                                                            72 69 74 65 64 00 )                               // rited.

Aquí tenemos la línea que indica el AttributeUsage mencionado anteriormente. Quedemos con esto porque es bastante importante… Pero mientras, sigamos:

   1: .field private initonly string shortDescription

   2:     .method public hidebysig specialname rtspecialname 

   3:             instance void  .ctor(string shortDescription,

   4:                                  string longDescription) cil managed

   5:     {

   6:       // Code size       18 (0x12)

   7:       .maxstack  8

   8:       IL_0000:  ldarg.0

   9:       IL_0001:  ldarg.2

  10:       IL_0002:  call       instance void [System]System.ComponentModel.DescriptionAttribute::.ctor(string)

  11:       IL_0007:  nop

  12:       IL_0008:  nop

  13:       IL_0009:  ldarg.0

  14:       IL_000a:  ldarg.1

  15:       IL_000b:  stfld      string Blog.AttributeToCil.Program/CustomDescriptionAttribute::shortDescription

  16:       IL_0010:  nop

  17:       IL_0011:  ret

  18:     } // end of method CustomDescriptionAttribute::.ctor

Aquí tenemos el resto de la clase. No hay diferencia alguna con cualquier otro tipo de clase en MSIL. Una vez tenemos esto, vamos a ver cómo queda la propiedad Name de la clase Person a la que le añadimos el atributo:

   1: .property instance string Name()

   2:    {

   3:      .custom instance void Blog.AttributeToCil.Program/CustomDescriptionAttribute::.ctor(string,

   4:                                                                                          string) = ( 01 00 0A 46 69 72 73 74 20 6E 61 6D 65 3B 41 20   // ...First name;A 

   5:                                                                                                      67 69 76 65 6E 20 6E 61 6D 65 20 6F 72 20 74 68   // given name or th

   6:                                                                                                      65 20 6E 61 6D 65 20 74 68 61 74 20 6F 63 63 75   // e name that occu

   7:                                                                                                      72 73 20 66 69 72 73 74 20 69 6E 20 61 20 67 69   // rs first in a gi

   8:                                                                                                      76 65 6E 20 6E 61 6D 65 2E 00 00 )                // ven name...

   9:      .get instance string Blog.AttributeToCil.Program/Person::get_Name()

  10:      .set instance void Blog.AttributeToCil.Program/Person::set_Name(string)

  11:    } // end of property Person::Name

Vemos el get/set y además, una referencia a una instancia del atributo que hemos creado. El mismo tipo de referencia que había en el caso anterior con el AttributeUsage. En CIL, .custom se usa para indicar CustomAttributes que es todo aquello que hereda de System.Attribute. Y CIL obtiene la información del atributo y la coloca además en el propio código generado, en el metadata asociado. La forma de instanciar un objeto de este tipo es por su nombre y un array de bytes en el que previamente se indican el tipo de objeto que irá en el constructor:

   1: .ctor(string,

   2:   string) = ( 01 00 0A 46 69 72 73 74 20 6E 61 6D 65 3B 41 20   // ...First name;A 

   3:               67 69 76 65 6E 20 6E 61 6D 65 20 6F 72 20 74 68   // given name or th

   4:               65 20 6E 61 6D 65 20 74 68 61 74 20 6F 63 63 75   // e name that occu

   5:               72 73 20 66 69 72 73 74 20 69 6E 20 61 20 67 69   // rs first in a gi

   6:               76 65 6E 20 6E 61 6D 65 2E 00 00 )                // ven name...

Con esto vemos que por un lado cada tipo mantiene una referencia a una instancia del tipo del atributo. Pero no la instancia en sí misma. La instancia se crea en cuanto se consulta la información del atributo. Si no hay nadie que la consulte, no se crea.

¿Cómo obtengo la información del atributo?

Por Reflection. Tenemos dos vías (en función del atributo que busquemos):

  • Obtener directamente el atributo empleando Reflection y navegando en los campos, métodos, etc para buscarlo.
  • Usar Attribute.GetCustomAttributes que nos permitirá dado un assembly, obtener los tipos de atributos que hemos creado para ello.

Afortunadamente hace un par de días publicaron una actualización que permite navegar por el código fuente de .NET de una forma más amena. Podemos encontrar como la clase Attribute y ver cómo obtiene los atributos por reflexión.

Por ejemplo, con el siguiente código podemos obtener el valor de CustomDescription que hemos creado anteriormente. Básicamente consiste en obtener la primera propiedad de la clase Person y preguntarle por sus CustomAttributes:

   1: var customDescription = person.GetType().GetProperties().First().GetCustomAttributes(true);

   2:  

* Si quieres saber más sobre atributos, puedes consultar la definición en el ECMA (sección II.22; página 205 y siguientes)

[C#] ¿Cómo funciona el Garbage Collector? ( III )

Siguiendo con la miniserie dedicada al Garbage Collector, hemos visto los siguientes puntos:

  • Primero sobre cómo se destruyen los objetos en C# y qué mecanismos e implicaciones hay.
  • Segundo, sobre cuándo se dispara el GC y qué efectos tiene sobre la memoria y el rendimiento.
  • Y en este tercero vamos a ver cómo podemos monitorizar el GC.

Notificaciones

El GC puede emitir notificaciones en estos casos:

  • Se ejecuta sobre una generación 2.
  • Pasan objetos de generación 1 a generación 2.

Por motivos de rendimiento no se registra el caso de objetos pequeños  Pero como hemos visto en el artículo anterior, si GC se dispara a menudo repercute en términos de rendimiento en nuestra aplicación. Y si además es porque tenemos objetos de generación 1/2 podemos añadir notificaciones a nuestra aplicación que nos permitirá ver cuándo se prepara y cuándo se ha ejecutado el GC. Veamos un ejemplo:

   1: GC.WaitForFullGCApproach();

Con este método paramos el hilo actual hasta que el GC esté preparado para lanzarse. Podemos hacer que sea un bloqueo indefinido o especificarle un tiempo en milisegundos de espera.

   1: GC.WaitForFullGCComplete();

Y con este otro método detenemos el hilo hasta que el GC completa su tarea en esa colección. Al igual que el método anterior podemos especificarle el tiempo máximo de espera en milisegundos.

Podemos crear un método que se encargue estar pendiente de estas notificaciones a través de un hilo:

   1: public static void WaitForFullGCProc()

   2: {

   3:     while (true)

   4:     {

   5:         while (true)

   6:         {

   7:             GCNotificationStatus s = GC.WaitForFullGCApproach();

   8:             if (s == GCNotificationStatus.Succeeded)

   9:             {

  10:                 Console.WriteLine("GC Notification raised.");

  11:             }

  12:             else if (s == GCNotificationStatus.Canceled)

  13:             {

  14:                 Console.WriteLine("GC Notification cancelled.");

  15:                 break;

  16:             }

  17:             else

  18:             {

  19:                 Console.WriteLine("GC Notification not applicable.");

  20:                 break;

  21:             }

  22:  

  23:             s = GC.WaitForFullGCComplete();

  24:             if (s == GCNotificationStatus.Succeeded)

  25:             {

  26:                 Console.WriteLine("GC Notifiction raised.");

  27:             }

  28:             else if (s == GCNotificationStatus.Canceled)

  29:             {

  30:                 Console.WriteLine("GC Notification cancelled.");

  31:                 break;

  32:             }

  33:             else

  34:             {

  35:                 Console.WriteLine("GC Notification not applicable.");

  36:                 break;

  37:             }

  38:         }

  39:  

  40:  

  41:         Thread.Sleep(500);

  42:  

  43:         if (finalExit)

  44:         {

  45:             break;

  46:         }

  47:     }

  48: }

Este método extremadamente feo y horroroso irá notificando las operaciones del GC y podremos ir viendo cuándo y en qué frecuencia se dispara. Es de notar que esto se llama sólo en caso de generación 1 y 2 y que además sólo en situaciones de bloqueo, por lo que si se llama muchas veces implica que quizá hay algo en nuestra aplicación que está consumiendo demasiados recursos.

Monitorización

Podemos configurar nuestra aplicación para que nos indique algunos datos sobre el consumo de memoria y CPU. A esto se le llama Application Resource Monitoring. Podemos activar la monitorización a través de la siguiente instrucción:

   1: AppDomain.MonitoringIsEnabled = true;

Una vez activada, no se puede desactivar. Aquí tenemos los contadores disponibles:

   1: AppDomain.MonitoringSurvivedProcessMemorySize

Con esto podemos ver cuánta es la memoria gestionada por el proceso que ha sobrevivido a una ejecución del GC.

   1: AppDomain.CurrentDomain.MonitoringSurvivedMemorySize

Y con esta lo mismo que la anterior pero referida al Application Domain. Tanto esta instrucción como la anterior toman en consideración la última aplicación del algoritmo en una fase bloqueante. Traduciendo, significa que se ha invocado a GC.Collect() de modo explícito o directamente lo ha hecho el hilo.

   1: AppDomain.CurrentDomain.MonitoringTotalAllocatedMemorySize

Con esta podemos ver cuántos bytes hemos alojado durante la vida de la aplicación. Hasta aquí todas las operaciones que permiten evaluar la memoria destinada en la aplicación y el proceso.

   1: AppDomain.CurrentDomain.MonitoringTotalProcessorTime

Y esta última el tiempo en el que la CPU está ejecutando el hilo de Application Domain. Veamos un ejemplo con el siguiente código, al que le pondremos estos contadores con el CustomObject de artículos anteriores:

   1: for (int i = 0; i < 10000; i++)

   2: {

   3:     var customObject = new CustomObject();

   4: }

Que produce la siguiente gráfica:

image

Vemos como la memoria total del proceso va en aumento hasta que entra el GC en su primera recolecta en la iteración 871. A partir de ahí, la memoria total crece pero el GC se encarga de ir recolectando. Si suprimimos esta variable para ver con un poco más de nitidez el resto, nos encontramos con esto:

image

Donde se aprecia los pequeños saltos que va optimizando el GC en las recolectas. Y un poco más en detalle:

image

Con lo que tenemos otro método para ver cuánta memoria consumimos y cómo el GC la va organizando. Si vemos que conforme la vida de nuestra aplicación la memoria aumenta y el coste de CPU también puede ser un síntoma de algo que no estamos haciendo bien.

Este es el último artículo de la “serie”. A modo de resumen hemos visto los siguientes puntos del Gargabe Collector:

  • En el primer artículo, hemos visto cómo se finaliza un objeto dentro de .NET Framework.
  • En el segundo, cómo se organizan los objetos internamente y el control algo “manual” del GC.
  • Y en este último sobre cómo podemos recibir notificaciones y medir la memoria empleada para averiguar si tenemos algún problema.