écrire du code de verrouillage serait assez facile, sauf pour ...
Comment voulez-vous récupérer? Voulez-vous être en mesure d'énumérer (foreach) sur la liste d'une manière thread-safe? Il y a plusieurs façons de faire cette partie, chacune avec des compromis.
Vous pourriez aller avec le comportement par défaut Cela ne fonctionnera probablement pas bien - vous obtiendrez une exception si quelqu'un change la liste pendant que vous l'énumérez.
Vous pouvez verrouiller la collection pendant toute la durée de l'énumération. Cela signifie que tout thread essayant d'ajouter à votre cache sera bloqué jusqu'à la fermeture de la boucle foreach.
Vous pouvez copier la collection en interne chaque fois que vous l'énumérez et enumerez la copie. Cela signifie que si quelqu'un ajoute à votre liste pendant que vous l'énumérez, vous ne verrez pas le changement.
Pour une liste de dix, j'irais avec la dernière option. (copie interne).
code Vous ressemblerait à quelque chose comme ceci:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Enumerator
{
class Program
{
static void Main(string[] args)
{
MyCache<string> cache = new MyCache<string>();
cache.Add("test");
foreach (string item in cache)
Console.WriteLine(item);
Console.ReadLine();
}
}
public class MyCache<T>: System.Collections.IEnumerable
{
private readonly LinkedList<T> InternalCache = new LinkedList<T>();
private readonly object _Lock = new Object();
public void Add(T item)
{
lock (_Lock)
{
if (InternalCache.Count == 10)
InternalCache.RemoveLast();
InternalCache.AddFirst(item);
}
}
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
// copy the internal cache to an array. We'll really be enumerating that array
// our enumeration won't block subsequent calls to Add, but this "foreach" instance won't see Adds either
lock (_Lock)
{
T[] enumeration = new T[InternalCache.Count];
InternalCache.CopyTo(enumeration, 0);
return enumeration.GetEnumerator();
}
}
#endregion
}
}
< B> EDIT 1: </B> Après avoir partagé quelques commentaires avec Rob Levine (ci-dessous), je pensais que je jetterais un autre couple alternatives là-bas.
Cette version vous permet d'itérer la collection sans verrou. Cependant, la méthode Add() est un peu plus chère, car elle doit copier la liste (déplacer la dépense hors de l'Enumerate, et sur l'ajouter).
public class Cache2<T>: IEnumerable<T>
{
// changes occur to this list, and it is copied to ModifyableList
private LinkedList<T> ModifyableList = new LinkedList<T>();
// This list is the one that is iterated by GetEnumerator
private volatile LinkedList<T> EnumeratedList = new LinkedList<T>();
private readonly object LockObj = new object();
public void Add(T item)
{
// on an add, we swap out the list that is being enumerated
lock (LockObj)
{
if (this.ModifyableList.Count == 10)
this.ModifyableList.RemoveLast();
this.ModifyableList.AddFirst(item);
this.EnumeratedList = this.ModifyableList;
// the copy needs to happen within the lock, so that threaded calls to Add() remain consistent
this.ModifyableList = new LinkedList<T>(this.ModifyableList);
}
}
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
IEnumerable<T> enumerable = this.EnumeratedList;
return enumerable.GetEnumerator();
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
System.Collections.IEnumerable enumerable = this.EnumeratedList;
return enumerable.GetEnumerator();
}
#endregion
}
< B> Edit 2: </B> Dans le dernier exemple, nous avons eu une itération vraiment bon marché, avec le compromis étant un appel plus cher à Ajouter(). Ensuite, j'ai pensé à utiliser un ReaderWriterLockSlim (il s'agit d'un objet .Net 3.5 - l'ancienne version de ReaderWriterLock offrait des performances médiocres)
Avec ce modèle, la méthode Add() est moins chère que le modèle précédent (bien que l'option Ajouter encore doit prendre un verrou exclusif). Avec ce modèle, nous n'avons pas besoin de créer des copies de la liste. Lorsque nous énumérons la liste, nous entrons dans un readlock, qui ne bloque pas les autres lecteurs, mais bloque/est bloqué par les writers (appels à Add). En ce qui concerne quel modèle est le meilleur - cela dépend probablement de la façon dont vous utilisez le cache. Je recommanderais de tester et de mesurer.
public class Cache3<T> : IEnumerable<T>
{
private LinkedList<T> InternalCache = new LinkedList<T>();
private readonly System.Threading.ReaderWriterLockSlim LockObj = new System.Threading.ReaderWriterLockSlim();
public void Add(T item)
{
this.LockObj.EnterWriteLock();
try
{
if(this.InternalCache.Count == 10)
this.InternalCache.RemoveLast();
this.InternalCache.AddFirst(item);
}
finally
{
this.LockObj.ExitWriteLock();
}
}
#region IEnumerable<T> Members
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
this.LockObj.EnterReadLock();
try
{
foreach(T item in this.InternalCache)
yield return item;
}
finally
{
this.LockObj.ExitReadLock();
}
}
#endregion
#region IEnumerable Members
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
{
this.LockObj.EnterReadLock();
try
{
foreach (T item in this.InternalCache)
yield return item;
}
finally
{
this.LockObj.ExitReadLock();
}
}
#endregion
}
Bien que j'ai un peu aimé l'orthographe de .NET (.NOT) je l'ai changé pour plus de clarté. –
Qu'est-ce que vous demandez? Vous recherchez un cache thread-safe? –