Controles de origen de datos, 3 Parte: Acceso a datos asincrónico

Visual Studio 2005

Noviembre de 2005

Publicado: 30 de Enero de 2006

Nikhil Kothari
Microsoft Corporation

Este artículo se aplica a:
Microsoft Visual Studio 2005
Microsoft ASP.NET 2.0
Controles de origen de datos

Resumen: éste es el tercero de una serie de artículos sobre la creación de controles de origen de datos. En este artículo, Nikhil examina la forma de realizar y encapsular acceso a datos asincrónico, mientras presenta un marco de acceso a datos asincrónico reutilizable basado en lo que hay disponible en Microsoft Visual Studio 2005. (6 páginas impresas.)

Haga clic aquí para descargar el ejemplo de código de este artículo.

Este artículo apareció originalmente en el blog de Nihkil (en inglés); desde ahí puede unirse al debate.

En las partes 1 y 2, se creó un control WeatherDataSource que se ejecuta con la API XML que ofrece weather.com (en inglés) mediante WebRequest y WebResponse para tener acceso a los datos a través de HTTP. Hasta ahora, se tenía acceso al servicio sincrónicamente. Como resultado, el procesamiento de la página se bloqueaba hasta que finalizaba la solicitud Web. Esto funcionaba bien para páginas de prueba y quizás en pequeños sitios, pero lo hacía pésimamente en un sitio que recibiese tráfico decente, por ejemplo, una página de portal donde fuese común un módulo de información meteorológica.

En esta página

Introducción Introducción
Información general Información general
El marco El marco
El ejemplo El ejemplo
Acerca del autor Acerca del autor

Introducción

Hay un número fijo de subprocesos en el conjunto que se pueden utilizar para servir solicitudes y, desafortunadamente, la solución no radica simplemente en subir el límite (los subprocesos consumen recursos y la CPU también). Por tanto, mientras una página se encuentra bloqueada en espera de otro servidor, también consume un subproceso y posiblemente provoque que otras solicitudes entrantes tengan que esperar más tiempo en la cola. El resultado se traduce en tiempos de acceso al sitio más lentos y una reducción en el uso de la CPU. En Visual Studio 2005, hemos incluido páginas asincrónicas, que permiten que los controles definan las tareas que desean realizar asincrónicamente, es decir, sin bloquear el subproceso utilizado para procesar la solicitud. No me detendré aquí en la descripción de los detalles de las propias páginas asincrónicas; tanto Dmitry (en inglés) como Fritz Onion (en inglés) ya lo han hecho anteriormente. Lo que trataremos en este artículo será la forma de utilizar esta capacidad en controles de origen de datos, mediante un marco complementario para implementar los orígenes de datos asincrónicos.

Información general

En la parte 1, hice alusión a un diseño algo raro de la clase DataSourceView:

public abstract class DataSourceView {
    public virtual void Select(DataSourceSelectArguments arguments,
                               DataSourceViewSelectCallback callback);
    protected abstract IEnumerable ExecuteSelect(
        DataSourceSelectArguments arguments);

    ...
}

Advertirá que el método público Select no devuelve en realidad ningún dato. En su lugar, toma una devolución de llamada y devuelve los datos a través de ésta. Simplemente realiza una llamada a ExecuteSelect protegido (que siempre realiza el acceso a datos sincrónico) para recuperar los datos que entregará al control enlazado a datos. La implementación predeterminada de la clase DataSourceView realmente no hace nada de forma asincrónica. El motivo de esto es que no tenemos controles de origen de datos asincrónicos de fábrica. Pero el diseño de OM permite, de hecho, la implementación de acceso a datos asincrónico, donde los datos no se encuentran disponibles hasta que ha finalizado el trabajo asincrónico. De este modo, tenemos un modelo basado en devoluciones de llamada.

Aquellos familiarizados con las API asincrónicas en el marco advertirán la ausencia del patrón asincrónico: los métodos públicos Select, BeginSelect y EndSelect, donde el control enlazado a datos elige el método de llamada. Sin embargo, los controles de acceso a datos no tienen la capacidad de decidir si eligen la API sincrónica o la asincrónica. Además, sencillamente no tiene sentido agregar una propiedad en los controles enlazados a datos. Los controles de origen de datos encapsulan los detalles sobre cómo obtener acceso a un almacén de datos y la decisión sobre si ocurre sincrónica o asincrónicamente la debe tomar el origen de datos ya se base en la semántica o en una propiedad personalizable. El punto adecuado para una posible propiedad "bool PerformAsyncDataAccess" pertenece al propio control de origen de datos. Esto también permite que el control de origen de datos realice el acceso a datos empleando un único enfoque incluso si varios controles están enlazados a los mismos orígenes de datos. He tenido que explicar ya unas cuantas veces estos conceptos sutiles que hay tras la arquitectura; esperemos que esto clarifique el diseño.

Una nota final sobre las tareas asincrónicas: el desarrollador de páginas es el que tiene la última palabra sobre si la página debe realizar trabajo asincrónico (a través del atributo Async en la directiva Page). De ahí que cualquier control de origen de datos bien escrito debe degenerar para realizar un acceso a datos sincrónico según sea necesario.

El marco

En el marco (lo dejaré disponible con el resto del ejemplo al final de la serie), he colocado juntas las clases base AsyncDataSource y AsyncDataSourceView, que se pueden utilizar para implementar controles de origen de datos que pueden realizar el acceso a datos asincrónicamente. A continuación se ofrece una rápida descripción general de lo que contiene el marco, junto con los comentarios que lo explican:

public abstract class AsyncDataSourceControl : DataSourceControl, 
    IAsyncDataSource {
    private bool _performAsyncDataAccess;

    protected AsyncDataSourceControl() {
        _performAsyncDataAccess = true;
    }

    public virtual bool PerformAsyncDataAccess {
        get; set;
    }

    bool IAsyncDataSource.IsAsync {
        get { return _performAsyncDataAccess & Page.IsAsync; }
    }
}

public abstract class AsyncDataSourceView : DataSourceView {

    protected abstract IAsyncResult BeginExecuteSelect(
        DataSourceSelectArguments arguments,
        AsyncCallback asyncCallback,
        object asyncState);

    protected abstract IEnumerable EndExecuteSelect(
        IAsyncResult asyncResult);

    protected override IEnumerable ExecuteSelect(
        DataSourceSelectArguments arguments) {
        // Implement the abstract ExecuteSelect method 
        // inherited from DataSourceView
        // by using BeginExecuteSelect and EndExecuteSelect
        // to do synchronous data access
        // via blocking.
    }

    private IAsyncResult OnBeginSelect(object sender, 
        EventArgs e, AsyncCallback asyncCallback,
        object extraData);
    private void OnEndSelect(IAsyncResult asyncResult);

    public override void Select(DataSourceSelectArguments arguments,
                                DataSourceViewSelectCallback callback) {
        if (_owner.IsAsync) {
            // Call Page.RegisterAsyncTask using 
            // OnBeginSelect and OnEndSelect
            // as the BeginEventHandler and EndEventHandler
            // methods to indicate the
            // need to do async work. These methods in turn 
            // call on the specific
            // data source implementation by calling the 
            // abstract BeginExecuteSelect and
            // EndExecuteSelect methods that have been 
            // introduced in this class.
        }
        else {
            // Perform synchronous data access
            base.Select(arguments, callback);
        }
    }

    ...
}

El ejemplo

El nuevo AsyncWeatherDataSource se derivará ahora de AsyncDataSourceControl y AsyncWeatherDataSourceView lo hará de AsyncDataSourceView.

public class AsyncWeatherDataSource : AsyncDataSourceControl {

    // Identical to WeatherDataSource
}

private sealed class AsyncWeatherDataSourceView : AsyncDataSourceView {
    private AsyncWeatherDataSource _owner;
    private WeatherService _weatherService;

    public AsyncWeatherDataSourceView(AsyncWeatherDataSource owner, 
        string viewName)
        : base(owner, viewName) {
        _owner = owner;
    }

    protected override IAsyncResult BeginExecuteSelect(DataSourceSelectArguments arguments,
    AsyncCallback asyncCallback,
    object asyncState) {
        arguments.RaiseUnsupportedCapabilitiesError(this);

        string zipCode = _owner.GetSelectedZipCode();
        if (zipCode.Length == 0) {
            return new SynchronousAsyncSelectResult(/* selectResult */ 
                null,
                asyncCallback, asyncState);
        }

        _weatherService = new WeatherService(zipCode);
        return _weatherService.BeginGetWeather(asyncCallback, asyncState);
    }

    protected override IEnumerable EndExecuteSelect(IAsyncResult asyncResult) {
        SynchronousAsyncSelectResult syncResult = 
            asyncResult as SynchronousAsyncSelectResult;
        if (syncResult != null) {
            return syncResult.SelectResult;
        }
        else {
            Weather weatherObject = 
                 _weatherService.EndGetWeather(asyncResult);
            _weatherService = null;

            if (weatherObject != null) {
                return new Weather[] { weatherObject };
            }
        }

        return null;
    }
}

Lo esencial que debe tener en cuenta es que, al utilizar el marco, todo lo que tiene que implementar es BeginExecuteSelect y EndExecuteSelect. En su implementación, se llama normalmente a los métodos BeginXXX y EndXXX expuestos por varios objetos del marco como WebRequest o IO Stream (así como SqlDataCommand, también en Visual Studio 2005), y se devuelve el IAsyncResult que se obtiene. En el caso del ejemplo, dispongo de una clase auxiliar WeatherService que incluye el objeto subyacente WebRequest.

Para aquellos que realmente echan de menos el patrón asincrónico, lo podrán ver en acción aquí; los dos con BeginExecuteSelect y EndExecuteSelect implementados, así como los métodos Begin y End que se llaman para que se obtengan y devuelvan instancias de IAsyncResult.

La clase SynchronousAsyncSelectResult es probablemente la más interesante (estoy de acuerdo en que es un oxímoron en cierto sentido). Esta clase viene junto con el marco. Se trata básicamente de una implementación de IAsyncResult que contiene datos disponibles inmediatamente y registra el valor True desde su propiedad IAsyncResult.CompletedSynchronously. Por ahora, sólo se utiliza en el caso en el que no se selecciona ningún código postal y es necesario devolver el valor Null (no tiene sentido iniciar una tarea asincrónica para devolver simplemente el valor Null) pero, como verá en el siguiente artículo, esto también resultará útil en otros escenarios.

La infraestructura de la página oculta la mayoría de los detalles de hacer trabajo asincrónicamente en el contexto de Microsoft ASP.NET. Espero que el marco que proporcionaré haga posible escribir orígenes de datos que empleen la infraestructura con el mínimo esfuerzo. Sin embargo, implementar el comportamiento asincrónico es complejo por su propia naturaleza. A veces una lectura, seguida de algunas preguntas, y una segunda lectura permiten comprender la esencia. Puede enviar preguntas o discutir este aspecto mediante el formulario de comentarios que se muestra abajo. Y siga sintonizado para conocer un acceso a datos con mayor rendimiento que trataré en el próximo artículo.

Acerca del autor

Nikhil Kothari es arquitecto del equipo de herramientas y plataforma Web en Microsoft (que ofrece herramientas de desarrollo Web de IIS, ASP.NET y Visual Studio). Concretamente, es responsable del área de características generales de formularios Web (es decir, controles de servidor y marcos de página). Lleva trabajando en el equipo desde los primeros días de XSP y ASP+; antes de su función actual, dirigió el desarrollo del marco de la página y varios controles que se pueden encontrar hoy en día en el cuadro de herramientas de ASP.NET.

Mostrar: