.NET, Desarrollo

Extendiendo dinámicamente aplicaciones .NET con Microsoft Extensibility Framework (MEF)

En un artículo anterior veíamos como elevar el desacomplamiento de los componentes de nuestras aplicaciones usando el patrón Inyección de Dependencias de forma que construyamos sistemas fácilmente extensibles y mantenibles. En este artículo vamos un paso más y veremos como ligar los componentes hasta el momento de la ejecución. Para eso crearemos nuestra propia infraestructura de plugins usando Microsoft Extensibility Framework (MEF).

Sigue leyendo

.NET, Desarrollo

Componentes de UI reutilizables en ASP.NET MVC con Partial Views y Child Actions

Como desarrolladores al fin, llegamos mucho más rápido a la convicción de que debemos reutilizar el código que soporta la lógica de nuestra aplicación. Cuando contruimos aplicaciones web comúnmente empezamos a preocuparnos por la reutilización del código de la interfaz de usuario cuando el código HTML empieza a parecer una auténtica pasta italiana.

En ASP.NET MVC tenemos varias formas de crear componentes de UI reutilizabes. Cada forma es más o menos efectiva en determinadas situaciones. En este artículo vamos a revisar 2 herramientas que tenemos a la mano en ASP.NET MVC para crear componentes de UI reutilizables: Partial Views y Child Actions.

Sigue leyendo

.NET, Desarrollo

Subiendo imágenes en aplicaciones ASP.NET MVC con plupload y Bootstrap (Parte 1 de 2)

upload_images

En un artículo anterior veíamos como servir imágenes en nuestras aplicaciones ASP.NET MVC. Hoy vamos a ver como subir las imágenes a un repositorio para que estén disponibles para su posterior uso.  El repositorio que usaremos es Microsoft Azure Blob Storage y para subir la imagen desde el navegador vamos a emplear las librería plupload y Bootstrap.

Sigue leyendo

.NET, Desarrollo

Programación asíncrona para mejorar el desempeño de aplicaciones ASP.NET MVC

Empiezas a desarrollar tu aplicación y necesitas implementar alguna función que se ejecutará frecuentemente y que depende de un servicio externo (ejemplo subir documentos a Azure Storage). No sabes exactamente en que magnitud esta función va a afectar el desempeño de tu sistema pero es claro que necesitarás hacer algo para que cuando empiecen a entrar todos tus usuarios tu servidor web sufra lo menos posible y la experiencia sea positiva.

En este artículo vamos a revisar como la programación asíncrona puede mitigar el impacto de servicios de una alta latencia.

La programación asíncrona

Las técnicas de programación asíncrona surgen como una respuesta de los diseñadores de software a la necesidad de usar de una manera más eficiente las capacidades del hardware. El objetivo de estas técnicas es evitar que el hardware tenga tiempos muertos y por tanto esté ejecutando la mayor cantidad de instrucciones posible.

Veamos el ejemplo de las interfaces gráficas de usuario. Con técnicas de programación tradicional, el programa estaría continuamente ocupando al procesador preguntándole a cada uno de los controles si fueron accionados por el usuario. En su lugar, con las técnicas de programación asíncrona, la API de interfaz gráfica de usuario expone manejadores de eventos que el programa implementa para ser ejecutado cuando la API detecte que algún control fue accionado por el usuario. En el inter el programa está “dormido” sin ocupar el hardware de forma que se puede atender a otros programas que estén corriendo en el equipo.

El término “asíncrono” viene justamente del hecho de que todas las instrucciones del programa no se ejecutan secuencialmente o de manera síncrona, si no que se divide en fragmentos (ej. el código de los manejadores de eventos en las interfaces gráficas) que son ejecutados cuando se requiera. En otras palabras, no sabemos exactamente en que momento se va a ejecutar cada fragmento de un programa asíncrono.

El reto para el programador en está en hacer que cada uno de estos fragmentos ejecuten su cometido lo más pronto posible y retornen el control a la plataforma para que pueda liberarse el hilo de ejecución y así el hardware pueda atender con mayor eficiencia otras tareas en ejecución.

Programación asíncrona en .NET

Antes de irnos a las complejidades de las aplicaciones web es importante primero entender como se puede hacer programación asíncrona en .NET.

Vale comentar que desde su sus inicios, el .NET framework ha tenido diferentes acercamientos a la programación asíncrona. Actualmente, el modelo recomendado es el Task-based Syncronous Pattern (TAP) el cual es implementado en las clases System.Threading.Tasks.Task y System.Threading.Tasks.Task<TResult> que representan operaciones arbitrarias que deben ejecutarse asíncronamente. Además, a nivel del lenguaje, C# incorpora las instrucciones async and await que simplifican la utilización de TAP. Veamos un ejemplo:

using System;
using System.Threading.Tasks;

namespace TAM
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Iniciando el programa");
            GetCurrentTemperatureAsync();
            Console.WriteLine("Haciendo cualquier otra cosa (ej. imprimir este mensaje) ...");
            Console.ReadLine();
        }

        public static async Task GetCurrentTemperatureAsync()
        {
            Console.WriteLine("Haciendo la solicitud al servicio externo");
            int temp = await GetCurrentTemperatureFromExternalServiceAsync();
            Console.WriteLine("Ya respondió el servicio externo. La temperatura actual: {0}", temp);
        }

        public static async Task<int> GetCurrentTemperatureFromExternalServiceAsync()
        {
            await Task.Delay(2000); // simulando la demora para recibir la respuesta del servicio externo
            return 25;
        }
    }
}

En el ejemplo anterior se ilustran los elementos fundamentales de TAP. Veamos:

Primero centrémonos en el método GetCurrentTemperatureAsync. Puntos relevantes de este método son:

  • El valor de retorno es un objeto de tipo Task. Esto indica que, de invocarse de manera asíncrona, el método no retornará ningun valor.
  • El uso de la instrucción async para indica
  • r al compilador que el método es asíncrono. Es decir que todas las instrucciones del metodo no se ejecutan secuencialmente
  • El uso del sufijo Async al final del identificador del método. Lo anterior es una convención del TAP.
  • El método primer muestra el mensaje “Haciendo la solicitud al servicio externo” y posteriormente invoca al metodo GetCurrentTemperatureFromExternalServiceAsync usando la instrucción await. Esta instrucción simplemente inicia la llamada el método y devuelve el control a quien haya invocado a GetCurrentTemperatureAsync. Una vez que GetCurrentTemperatureFromExternalServiceAsync haya completado entonces el compilador regresará el control a GetCurrentTemperatureAsync y continuará su ejecución en la linea siguiente al await, es decir, la impresion del mensaje “Ya se recibió respuesta de …”

Analicemos ahora el metodo GetTemperatureFromExternalServiceAsync. Este método simula una llamada a un servicio externo de alta latencia que devuelve la temperatura actual. Veamos algunos puntos interesantes de este método:

  • El valor de retorno es un objeto de tipo Task. Esto indica que el metodo es asincrono y que de su ejecución deberia retornar un valor numérico entero (la temperatura actual).
  • Igualmente se usa la instrucción async y el prefijo Async.
  • La primera instrucción fuerza un retraso para simular la alta latencia del proceso.
  • Posteriormente se devuelve el valor de ejemplo 25. Este valor es el que quedará en la variable temp del método GetCurrentTemperatureAsync cuando transcurran los dos segundos de retraso.

Veamos ahora la función principal del programa:

  • Primero se muestra el mensaje “Iniciando el programa” e inmediatamente despues se invoca al método asíncono GetTemperatureAsync
  • El control de ejecución regresará aun cuando todavía el método GetTemperatureFromExternalServiceAsync no se haya completado y se mostrará el mensaje “Haciendo cualquier otra cosa (ej. imprimir este mensaje) …”

Para terminar de enteder la programación asincrona en .NET, es importante crees tu proyecto en VS, copies el codigo y user el debuger para ir paso por paso en la ejecución del programa.

El siguiente diagrama muestra toda la secuencia de ejecución.

execution_order

La siguiente imagen muestra el resultado de la ejecución del programa.

program_execution_result

Ahora que ya entendemos las bases de la programación asíncrona en .NET siguiendo el Task based Asyncronous Pattern veamos entonces como funciona esto en las aplicaciones ASP.NET MCV.

Asincronismo en ASP.NET MVC

En las aplicaciones web, el servidor web (IIS) dispone de varios hilos de ejecución que activa para atender a cada una de las solicitudes que recibe. A este conjunto de hilos disponibles se le conoce como “Thread Pool” y su tamaño puede establecerse en la configuración del servidor.

Un símil, una oficina de de trámites del gobierno

La dinámica de una aplicación web es comparable con lo que pasa en una oficina de trámites del gobierno.

Supongamos que la oficina tiene 5 funcionarios para atender a los ciudadanos. Si estos llegan muy esporádicamente a realizar trámites que son muy rápidos de completar entonces no habrá problemas y los ciudadanos considerarán a la oficina como un modelo de buen servicio. Pero que pasa si los ciudadanos llegaran con mayor frecuencia y algunos llegaran a realizar trámites que requieren mucho tiempo para completarse. En ese caso lo mas probable es que los los funcionarios llegarían rápidamente a saturarse y los ciudadanos tendrían que hacer fila por varias horas. La percepción del servicio de la oficina de trámites sería pésima.

La oficina de gobierno sería nuestro servidor web trabajando con .NET Framework, los funcionarios serían los diferentes hilos de ejecución y su trabajo coordinado con nuestra aplicación web, los ciudadanos serían las peticiones HTTP de los usuarios y los tramites serían cada una de las acciones implementadas por los controladores de nuestra aplicación ASP.NET MVC.

Si la aplicación tiene poca actividad y las solicitudes que llegan tardan milisegundos en ejecutarse entonces el servidor web podrá manejar las peticiones sin mayor problema. Pero en caso de que algunas peticiones tarden mucho en completarse podrían acumularse muchas peticiones y terminarse la cantidad de hilos de ejecución disponibles. Al igual que en el ejemplo de la oficina de tramites, esto se traducirá en un desempeño pobre de la aplicación.

Un ejemplo de peticiones rápidas de ejecutar serían acciones que ejecuten trabajos simples en memoria, un ejemplo de petición tardada de ejecutar podría ser una que requiere de interacción con servicios externos, ejemplo subir y bajar documentos a Azure Blob Storage.

Agilizando la oficina de gobierno

Para la oficina de trámites, una forma de agilizar el servicio podría ser:

  1. El funcionario recibe a un ciudadano que necesita realizar un trámite tardado, por ejemplo uno que requiriera la intervención de otro departamento.
  2. El funcionario inicia el trámite con el otro departamento y le pide al ciudadano que vaya a la sala de espera a que se reciba la respuesta del otro departamento
  3. El ciudadano se sienta a esperar mientras el funcionario sigue atiendo a otros ciudadanos.
  4. Cuando se recibe respuesta del departamento involucrado en el trámite, el funcionario llama de nuevo al ciudadano y completa el trámite.

Con esta estrategia el funcionario no tiene tiempos muertos y se pueden atender a más ciudadanos en el día. Esto es junto lo que se logra al aplicar técnicas de programación asíncrona en las aplicaciones ASP.NET MVC.

Acciones asíncronas en controladores ASP.NET MVC

Aplicando la estrategia anterior a aplicaciones web, lo que deberiamos ver es que cuando la aplicación recibe una petición de llevar a cabo una acción tardada que requiere de un servicio externo, en lugar de esperar a la respuesta del servicio externo bloqueando un hilo de ejecución, simplemente enviaría la petición al servicio externo y devolvería el control al .NET Framework para que se libere el hilo de ejecución del servidor web y que pueda ser utilizado para atender otras peticiones. Cuando se haya recibido la respuesta del servicio externo entonces el .NET Framework reanudará la ejecución de la acción hasta completarla.

Como seguro ya te imaginas, ASP.NET MVC usa el modelo actualmente recomendado para programación asíncrona en .NET, el TAP. Dado que ya explicamos anteriormente como funciona TAP vamos directo a analizar el código de una acción asíncrona.

public class ProjectTaskController : ControllerBase
{

	...
	[HttpPost]
        [ValidateAntiForgeryToken]
        public async Task Complete(CompletedProjectTaskViewModel completedTask)
        {
		...
		TaskExecutionResultInfo res = await ProjectsSrv.CompleteProjectTaskAsync(...);
		...
	}

	...
}

En el fragmento de código anterior tenemos un controlador llamado ProjectTaskController que se encarga de manejar las tareas de proyectos de gestión de documentos. Una de sus acciones es Complete la cual recibe un view model de tipo CompletedProjectTaskViewModel que trae desde la vista toda la información necesaria para registrar que se ha completado una tarea de un proyecto. Algunas cosas interesantes de esta acción son:

  • La accion esta marcada con la instrucción async la cual le indica al compilador que la acción es asíncrona.
  • En lugar de regresar el tradicional ActionResul, en su lugar el valor de retorno de la acción es de tipo Task<ActionResult>
  • Al invocar al metodo ProjectsSrv.CompleteProjectTaskAsync se usa la instrucción await. Esto le indica al compilador que, en lo que se completa la ejecución de ProjectsSrv.CompleteProjectTaskAsync el hilo de ejecución del servidor web que se está ocupando para atender la petición puede liberarse para atender otras peticiones. EN ESTE PUNTO ES QUE SE OBTIENE LA MEJORA EN EL RENDIMIENTO DE LA APLICACIÓN AL MITIGAR EL RIESGO DE QUE SE CONSUMAN TODOS LOS HILOS DE EJECUCIÓN DISPONIBLES EN EL SERVIDOR WEB. Es importante destacar que una solicitud no va a terminar mas rapido pero, si le eleva la probabilidad de que se procesen mas solicitudes en el tiempo derivado del hecho de que no habrá hilos ocupados innecesariamente.
  • Como se verá a continuación, el valor de retorno de ProjectsSrv.CompleteProjectTaskAsync es un Task<TaskExecutionResult>, sin embargo gracias a la instrucción await, lo que quedará en la variable res es solo un TaskExecutionResult.
public class ProjectsService : DataService, IProjectsService
{
	...
	public async Task CompleteProjectTaskAsync(...)
	{
		...
		Guid documentContentId = await _documentContectSrv.AddAsync(..);
		...
		return new TaskExecutionResultInfo { DocumentVersion = dv, TaskExecutionResult = res } ;
	}
	...
}

En el fragmento anterior se muestra el servicio ProjectsService el cual ofrece el soporta el manejo de proyectos. Este tiene el método CompleteProjectTaskAsync el cual permite registrar que se ha completado una tarea y que debe almacenarse un nuevo documento. Algunos aspectos interesantes de este método son:

  • De nuevo el uso de la instrucción async y el sufijo Async.
  • El valor de retorno es de tipo Task<TaskExecutionResult>.
  • El uso de la instrucción await para indicar al compilador que debe regresarse al metodo que llamó a la función (en este caso a la acción ProjectTaskController.Complete) y regresar a la siguiente linea cuando _document.ContentSrv.AddAsync haya completado.
  • Como se verá a continuación, el valor de retorno de _documentContentSrv es Task<Guid>. No obstante, el valor que quedrá en documentContentId será un Guid.
public class AzureDocumentContentService : IDocumentContentService
{
	...
	public async Task AddAsync(byte[] content, string fileExt, string title, int accountId)
        {
		...
		await blockBlob.UploadFromStreamAsync(stream);
		...
	}
	...
}

El fragmento anterior muestra el servicio AzureDocumentContentService el cual permite subir documentos a una cuenta de Azure Storage. Este servicio tiene el método AddAsync que permite subir un documento a Azure Storage. Algunos puntos relevantes de este método son:

  • De nuevo, el uso de la instrucción async y el sufijo Async.
  • El valor de retorno es un Task<Guid>
  • El uso de la instrucción await para indicar al compilador que debe regresarse al metodo que llamó a la función (en este caso a la acción ProjectsService.CompleteProjectTaskAsync) y regresar a la siguiente linea cuando blockBlob.UploadFromStreamAsync haya completado.

Conclusiones

Usada adecuadamente la programación asincrona beneficia el rendimiento de las aplicaciones web al promover un uso mas eficiente de los recursos del servidor cuando se requiere interacción con servicios externos de alta latencia.

El .NET Framework y C# cuentan con clases y estructuras del lenguaje que hacer que introducir tecnicas de programación asincrona sea casi tan simple como mantenernos en las tenicas tradicionales de programacion.

Como es lógico, no debe abusarse de esta ténica dado que la programación asincrona tienden a hacer el codigo mas complejo de entender y mantener.

Si quieres mas información sobre este tema: