AsyncController en ASP.NET MVC 2

Una de las novedades previstas para ASP.NET MVC 2 será la posibilidad de crear controladores asíncronos. Por el momento, podemos hacer la primera toma de contacto con la versión Release Candidate de esta versión.

¿Qué conseguimos con los controladores asíncronos?

Imaginemos que necesitamos lanzar una serie de procesos con una duración más prolongada en el tiempo de lo normal. Cuando una petición llega al servidor, uno de los hilos se encarga de la misma. Hasta que la petición no finalice, ese hilo quedará bloqueado y no podrá atender otras peticiones.

En la mayoría de las ocasiones no resulta problemático dado que el pool tiene una cantidad considerable de hilos disponibles para ser bloqueados. Sin embargo, los hilos son limitados y en aplicaciones grandes, donde hay una cantidad considerable de usuarios y pueden existir múltiples hilos con procesos de larga duración, puede llegar a ser un problema produciendo un error de tipo HTTP 503 (Server too busy).

Antes de comenzar, es necesario tener en cuenta una serie de reglas a la hora de crear un controlador de este tipo:

  1. Se crean dos métodos por acción:
    • Uno iniciará el proceso de manera asíncrona y debe contener en su nombre la terminación Async.
    • Otro método, que será invocado cuando la operación asíncrona finalice, será nombrado con  Completed al final.
  2. Ambos métodos deben ser públicos y no pueden ser estáticos.
  3. No está permitido tener parámetros genéricos en las acciones.
  4. No podemos usar sobrecarga de parámetros.Solamente está permitido en los casos que se especifica otro Http Verb o declarando la sobrecarga como NoActionAttribute.
  5. El controlador heredará de AsyncController en vez de Controller.

Para verlo más claro, he creado una pequeña aplicación simulando un cliente de la red social Twitter, utilizando la librería Twitterizer. Las acciones que se podrán realizar a través de la aplicación serán las siguientes:

Para todos estos casos, necesitamos recuperar una serie de datos de la red social que podrá tardar más o menos en función de un conjunto de circunstancias. Si varios usuarios accedieran a la vez a nuestra aplicación y la tarea de recuperar sus tweets, direct messages, mentions, etcétera fuera una tarea costosa, llegaría un momento en el que nuestro servidor no dispondría de threads libres para cubrir el número de peticiones actuales.

Siguiendo las reglas comentadas anteriormente, una de las acciones del controlador podría tener la siguiente estructura:

using System.Collections.Generic;
using System.Web.Mvc;
using AsyncControllerMVC2.Models;
using AsyncControllerMVC2.Models.Objects;

namespace AsyncControllerMVC2.Controllers
{
public class TweetController : AsyncController
{
private readonly TwitterService _twitterService = new TwitterService();

public void TweetsAsync()
{
AsyncManager.OutstandingOperations.Increment();
AsyncManager.Parameters["tweets"] = _twitterService.FetchTweets();
AsyncManager.OutstandingOperations.Decrement();
}

public ActionResult TweetsCompleted(List<TwitterObject> tweets)
{
return View(tweets);
}
}
}

En primer lugar, observamos que efectivamente el controlador hereda de AsyncController para poder ofrecernos sus ventajas. Gracias a esta herencia, disponemos de AsyncManager que nos dará soporte en las operaciones asíncronas.

TweetsAsync será el método encargado de recuperar los tweets. En él podemos ver que se hace uso de OutstandingOperations, que se utiliza para notificar a ASP.NET de cuántas operaciones tiene pendientes en ese momento. Tal y como nos comentan en la documentación de MSDN, es necesario utilizarlo al comienzo del método ya que ASP.NET no es capaz de determinar cuántas operaciones fueron iniciadas por la acción o cuándo fueron completadas.

Entre medias, se realiza la llamada a un repositorio, donde se hace uso de la librería Twitterizer para recuperar el timeline de los amigos del usuario, y se guarda el resultado de la llamada en la propiedad Parameters de tipo diccionario.

using System;
using System.Collections.Generic;
using System.Threading;
using AsyncControllerMVC2.Models.Objects;
using Twitterizer.Framework;

namespace AsyncControllerMVC2.Models
{
public class TwitterService: ITwitterService
{
private const string User = "YOUR USER";
private const string Pass = "YOUR PASS";
private readonly TimeSpan _waitTime = new TimeSpan(0, 2, 0);
private readonly Twitter _twitterAccount;
public TwitterService()
{
_twitterAccount = new Twitter(User, Pass);
}

public List<TwitterObject> FetchTweets()
{
var tweets = new List<TwitterObject>();
foreach (TwitterStatus statusFriend in _twitterAccount.Status.FriendsTimeline())
{
var tweet = new TwitterObject
{
User = statusFriend.TwitterUser.UserName,
Photo = statusFriend.TwitterUser.ProfileImageUri,
Text = statusFriend.Text

};
tweets.Add(tweet);
}
Thread.Sleep(_waitTime);
return tweets;
}
}
}

Para crear un proceso con una duración considerable generé un TimeSpan con un minuto de duración para que, cuando consiga los últimos tweets de la cuenta en cuestión, espere durante un minuto antes de ser retornados de nuevo al controlador.

Cuando la propiedad OutstandingOperations sea igual a cero, ASP.NET finaliza las operaciones asíncronas y llama a la acción TweetCompleted que retorna el resultado recibido como parámetro. Para poder recibir el resultado almacenado en Parameters, es necesario respetar el tipo del objeto guardado.

Con los controladores asíncronos no significa que disminuya el tiempo de espera pero conseguiremos que el servidor no quede completamente bloqueado esperando a los hilos pendientes.

Adjunto el proyecto con todas las acciones de manera asíncrona.

Fuente: http://msdn.microsoft.com/en-us/library/ee728598%28VS.100%29.aspx

4 comentarios en “AsyncController en ASP.NET MVC 2”

  1. Hola cprieto,

    En primer lugar, gracias por tu comentario. Lo poco que he me ha dado tiempo a leer del doc es que en esa versión estaba la clase de un modo experimental, aunque revisaré el mismo de todos modos =)
    Al menos, en la versión Beta de ASP.NET MVC 2 se ofreció como feature y no como mejora…

    http://aspnet.codeplex.com/wikipage?title=Road%20Map&referringTitle=MVC

    Igualmente lo revisaré, me dejaste con la duda.

    Gracias de nuevo 😉

    ¡Saludos!

  2. Si, es cierto Cristian =)

    Pero es por ello que tendremos que esperar a la versión final de 2.0 para disfrutar de ellas de una manera más eficaz… O empezar a trastear con la RC =)

    ¡Gracias por tu comentario!

    ¡Saludos!

Deja un comentario

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