Share via


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.