Techniques de mise en cache d’objets
Dernière modification : dimanche 17 janvier 2010
S’applique à : SharePoint Foundation 2010
De nombreux développeurs utilisent les objets de mise en cache du Microsoft .NET Framework (par exemple System.Web.Caching.Cache) pour tirer le meilleur parti de la mémoire et améliorer les performances globales du système. Toutefois, de nombreux objets ne sont pas thread-safe et la mise en cache de ces objets peut provoquer l’échec des applications et des erreurs utilisateur inattendues ou sans rapport.
Notes
Les techniques de mise en cache présentées dans cette section sont différentes des options de mise en cache personnalisées pour la gestion du contenu Web abordées dans Vue d'ensemble de la mise en cache personnalisée.
Mise en cache des données et des objets
La mise en cache est un bon moyen d’améliorer les performances du système. Toutefois, il faut comparer les avantages de la mise en cache à la nécessité de sécurité des threads, étant donné que certains objets SharePoint ne sont pas thread-safe et que la mise en cache provoque un comportement inattendu de ces objets.
Mise en cache d’objets SharePoint qui ne sont pas thread-sage
Vous pouvez essayer d’augmenter les performances et l’utilisation de la mémoire en mettant en cache les objets SPListItemCollection qui sont retournés à partir de requêtes. En général, il s’agit d’une bonne pratique ; toutefois, l’objet SPListItemCollection contient un objet incorporé SPWeb qui n’est pas thread-safe et ne doit pas être mis en cache.
Par exemple, supposons que l’objet SPListItemCollection est mis en cache dans un thread. Lorsque d’autres threads essaient de lire cet objet, l’application peut échouer ou se comporter de façon étrange car l’objet SPWeb incorporé n’est pas thread-safe. Pour plus d’informations sur l’objet SPWeb et sur la sécurité des threads, voir la classe Microsoft.SharePoint.SPWeb.
Les instructions fournies dans la section suivante décrivent comment éviter les problèmes qui se produisent lorsque vous mettez en cache des objets SharePoint qui ne sont pas thread-safe dans un environnement multithread.
Comprendre les inconvénients potentiels de la synchronisation de threads
Vous pouvez ne pas avoir connaissance que votre code s’exécute dans un environnement multithread (par défaut, les services Internet (IIS) sont multithread) ou ne pas savoir comment gérer cet environnement. L’exemple suivant montre du code qui est parfois utilisé pour mettre en cache des objets Microsoft.SharePoint.SPListItemCollection qui ne sont pas thread-safe.
Pratique de codage incorrecte
Mise en cache d’un objet que plusieurs threads peuvent lire
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
L’utilisation du cache dans l’exemple précédent est fonctionnellement correcte ; toutefois, dans la mesure où l’objet de cache ASP.NET est thread-safe, elle présente des problèmes de performances potentiels. (Pour plus d’informations sur la mise en cache ASP.NET, voir la classe Cache). Si la requête dans l’exemple précédent prend 10 secondes, de nombreux utilisateurs pourraient tenter d’accéder simultanément à cette page pendant ce laps de temps. Dans ce cas, tous les utilisateurs exécuteraient la même requête, qui tenterait de mettre à jour le même objet de cache. Si cette même requête s’exécute 10, 50 ou 100 fois, avec plusieurs threads tentant de mettre à jour le même objet en même temps, en particulier sur les ordinateurs multitraitement et hyperthreaded, les problèmes de performance peuvent devenir particulièrement sérieux.
Pour empêcher que plusieurs requêtes n’accèdent aux mêmes objets simultanément, vous devez modifier le code comme suit.
Application d’un verrou
Vérification de la valeur 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
Vous pouvez accroître légèrement les performances en plaçant le verrou à l’intérieur du bloc de code if(oListItems == null). Dans ce cas, il est inutile de suspendre tous les threads lorsque vous vérifiez si les données sont déjà mises en cache. Selon la durée nécessaire à la requête pour renvoyer les données, il est toujours possible que plusieurs utilisateurs exécutent la requête en même temps. Ceci est particulièrement vrai si vous utilisez des ordinateurs multiprocesseurs. N’oubliez pas que plus le nombre de processeurs est grand et plus la durée d’exécution de la requête est longue, plus il est probable que le placement du verrou dans le bloc de code if() provoque des problèmes. Pour vous assurer qu’un autre thread n’a pas créé de oListItems avant que le thread en cours ait la possibilité de travailler dessus, vous pouvez utiliser le modèle suivant.
Application d’un verrou
Nouvelle vérification de la valeur Null
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 le cache est déjà rempli, les performances de ce dernier exemple sont aussi bonnes que celles de l’implémentation initiale. Si le cache n’est pas rempli et que le système est sous une charge légère, l’acquisition du verrou provoque une légère dégradation des performances. Cette approche devrait améliorer sensiblement les performances lorsque le système est sous une charge importante, car la requête est exécutée une seule fois au lieu de plusieurs fois, et les requêtes sont généralement coûteuses comparées au coût de la synchronisation.
Le code dans ces exemples interrompt tous les autres threads dans une section critique s’exécutant dans les services Internet (IIS) et empêche les autres threads d’accéder à l’objet mis en cache jusqu’à ce qu’il soit complètement créé. Cela résout le problème de synchronisation de thread ; cependant, le code n’est toujours pas correct car il met en cache un objet qui n’est pas thread-safe.
Pour résoudre le problème de sécurité des threads, vous pouvez mettre en cache un objet DataTable créé à partir de l’objet SPListItemCollection. Il faut alors modifier l’exemple précédent comme suit afin que votre code obtienne les données à partir de l’objet DataTable.
Pratique de codage correcte
Mise en cache d’un objet 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
Pour plus d’informations et des exemples d’utilisation de l’objet DataTable et d’autres bonnes idées pour le développement d’applications SharePoint, voir la rubrique de référence de la classe DataTable.