Técnicas de almacenamiento en caché de objetos
Última modificación: domingo, 17 de enero de 2010
Hace referencia a: SharePoint Foundation 2010
Muchos programadores usan los objetos de almacenamiento en caché de Microsoft .NET Framework (por ejemplo, System.Web.Caching.Cache) para aprovechar mejor la memoria y aumentar el rendimiento general del sistema. Sin embargo, muchos objetos no son "seguros para subprocesos", y el almacenamiento en caché de dichos objetos puede hacer que las aplicaciones fallen y puede causar errores de usuario inesperados o no relacionados.
Nota
Las técnicas de almacenamiento en caché que se tratan en esta sección son diferentes de las opciones de almacenamiento en caché personalizadas para administración de contenido web que se tratan en Introducción al almacenamiento en memoria caché personalizado.
Almacenamiento en caché de objetos y datos
El almacenamiento en caché es una buena forma de mejorar el rendimiento del sistema. Sin embargo, debe sopesar las ventajas del almacenamiento en caché frente a la necesidad de seguridad para subprocesos, ya que algunos objetos de SharePoint no son seguros para subprocesos, y el almacenamiento en caché hace que actúen de maneras inesperadas.
Almacenamiento en caché de objetos de SharePoint que no son seguros para subprocesos
Puede intentar aumentar el rendimiento y el uso de memoria mediante el almacenamiento en caché de objetos SPListItemCollection devueltos a partir de consultas. En general, este es un procedimiento recomendado; sin embargo, el objeto SPListItemCollection contiene un objeto SPWeb incrustado que no es seguro para subprocesos y no se debe almacenar en caché.
Por ejemplo, suponga que el objeto SPListItemCollection está almacenado en caché en un subproceso. Cuando otros subprocesos intenten leer este objeto, la aplicación puede fallar o comportarse de manera extraña porque el objeto incrustado de SPWeb no es seguro para subprocesos. Para obtener más información acerca del objeto SPWeb y la seguridad de subprocesos, vea la clase Microsoft.SharePoint.SPWeb.
En las instrucciones de la sección siguiente se describe cómo evitar problemas producidos al almacenar en caché los objetos de SharePoint que no son seguros para subprocesos en un entorno multiproceso.
Descripción de los posibles inconvenientes de la sincronización de subprocesos
Es posible que no sea consciente de que su código se está ejecutando en un entorno multiproceso (de manera predeterminada, Internet Information Services, o IIS, son multiproceso) o que no sepa cómo administrar ese entorno. En el ejemplo siguiente, se muestra código que a veces se usa para almacenar en caché objetos de Microsoft.SharePoint.SPListItemCollection que no son seguros para subprocesos.
Procedimiento no recomendado de codificación
Almacenamiento en caché de un objeto que varios subprocesos podrían leer
public void CacheData()
{
SPListItemCollection oListItems;
oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
if(oListItems == null)
{
oListItems = DoQueryToReturnItems();
Cache.Add("ListItemCacheName", oListItems, ..);
}
}
Public Sub CacheData()
Dim oListItems As SPListItemCollection
oListItems = CType(Cache("ListItemCacheName"), SPListItemCollection)
If oListItems Is Nothing Then
oListItems = DoQueryToReturnItems()
Cache.Add("ListItemCacheName", oListItems,..)
End If
End Sub
El uso de la memoria caché en el ejemplo anterior es funcionalmente correcto; sin embargo, puesto que el objeto de caché ASP.NET es seguro para subprocesos, presenta posibles problemas de rendimiento. Para obtener más información acerca del almacenamiento en caché de ASP.NET, vea la clase Cache). Si la consulta del ejemplo anterior tarda 10 segundos en completarse, es posible que varios usuarios intenten tener acceso a esa página simultáneamente durante ese período de tiempo. En este caso, todos los usuarios podrían ejecutar la misma consulta, que intentaría actualizar el mismo objeto de caché. Si esa misma consulta se ejecutara 10, 50 ó 100 veces, con varios subprocesos que intentaran actualizar el mismo objeto al mismo tiempo (especialmente en equipos multiproceso con tecnología Hyperthread), los problemas de rendimiento serían muy graves.
Para impedir que varias consultas tengan acceso simultáneamente a los mismos objetos, debe cambiar el código de la manera siguiente.
Aplicación de un bloqueo
Búsqueda de null
private static object _lock = new object();
public void CacheData()
{
SPListItemCollection oListItems;
lock(_lock)
{
oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
if(oListItems == null)
{
oListItems = DoQueryToReturnItems();
Cache.Add("ListItemCacheName", oListItems, ..);
}
}
}
Private Shared _lock As New Object()
Public Sub CacheData()
Dim oListItems As SPListItemCollection
SyncLock _lock
oListItems = CType(Cache("ListItemCacheName"), SPListItemCollection)
If oListItems Is Nothing Then
oListItems = DoQueryToReturnItems()
Cache.Add("ListItemCacheName", oListItems,..)
End If
End SyncLock
End Sub
Puede aumentar ligeramente el rendimiento si coloca el bloqueo dentro del bloque de código if(oListItems == null). Al hacerlo, no es necesario suspender todos los subprocesos mientras se comprueba que los datos ya están almacenados en caché. En función del tiempo que la consulta tarda en devolver los datos, es posible que varios usuarios estén ejecutando la consulta al mismo tiempo. Esto se produce sobre todo si utiliza equipos multiprocesador. Recuerde que cuanto mayor sea la cantidad de procesadores que se ejecutan y más tiempo tarde consulta en ejecutarse, será más probable que la colocación del bloqueo en el bloque de código de if() cause problemas. Para asegurarse de que otro subproceso no haya creado oListItems antes de que el subproceso actual tenga la oportunidad de trabajar en él, puede usar el modelo siguiente.
Aplicación de un bloqueo
Búsqueda de null de nuevo
private static object _lock = new object();
public void CacheData()
{
SPListItemCollection oListItems;
oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
if(oListItems == null)
{
lock (_lock)
{
// Ensure that the data was not loaded by a concurrent thread
// while waiting for lock.
oListItems = (SPListItemCollection)Cache["ListItemCacheName"];
if (oListItems == null)
{
oListItems = DoQueryToReturnItems();
Cache.Add("ListItemCacheName", oListItems, ..);
}
}
}
}
Private Shared _lock As New Object()
Public Sub CacheData()
Dim oListItems As SPListItemCollection
oListItems = CType(Cache("ListItemCacheName"), SPListItemCollection)
If oListItems Is Nothing Then
SyncLock _lock
' Ensure that the data was not loaded by a concurrent thread
' while waiting for lock.
oListItems = CType(Cache("ListItemCacheName"), SPListItemCollection)
If oListItems Is Nothing Then
oListItems = DoQueryToReturnItems()
Cache.Add("ListItemCacheName", oListItems,..)
End If
End SyncLock
End If
End Sub
Si la memoria caché ya está llena, este último ejemplo tendrá tan buenos resultados como la implementación inicial. Si la memoria caché no está llena y el sistema se encuentra bajo una carga ligera, la adquisición del bloqueo provocará una ligera disminución del rendimiento. Este método debería mejorar considerablemente el rendimiento cuando el sistema está bajo una carga elevada, porque la consulta se ejecuta solo una vez en lugar de varias veces, y las consultas son normalmente costosas en comparación con el costo de sincronización.
El código de estos ejemplos suspende todos los demás subprocesos en una sección crítica que se ejecute en IIS y evita que otros subprocesos tengan acceso al objeto almacenado en caché hasta que esté completamente creado. Esto soluciona el problema de sincronización de subprocesos; sin embargo, el código todavía no es correcto porque está almacenando en caché un objeto que no es seguro para subprocesos.
Para enfrentar la seguridad de subprocesos, puede almacenar en caché un objeto DataTable creado a partir del objeto SPListItemCollection. Deberá modificar el ejemplo anterior como sigue para que el código obtenga los datos del objeto DataTable.
Procedimiento recomendado de codificación
Almacenamiento en caché de un objeto DataTable
private static object _lock = new object();
public void CacheData()
{
DataTable oDataTable;
SPListItemCollection oListItems;
lock(_lock)
{
oDataTable = (DataTable)Cache["ListItemCacheName"];
if(oDataTable == null)
{
oListItems = DoQueryToReturnItems();
oDataTable = oListItems.GetDataTable();
Cache.Add("ListItemCacheName", oDataTable, ..);
}
}
}
Private Shared _lock As New Object()
Public Sub CacheData()
Dim oDataTable As DataTable
Dim oListItems As SPListItemCollection
SyncLock _lock
oDataTable = CType(Cache("ListItemCacheName"), DataTable)
If oDataTable Is Nothing Then
oListItems = DoQueryToReturnItems()
oDataTable = oListItems.GetDataTable()
Cache.Add("ListItemCacheName", oDataTable,..)
End If
End SyncLock
End Sub
Para obtener más información y ejemplos sobre cómo usar el objeto DataTable, además de otros procedimientos recomendados para desarrollar aplicaciones de SharePoint, vea el tema de referencia para la clase DataTable.