2008-10-09 2 views
5

J'ai quelques problèmes sur un site avec l'accès simultané à une liste. Cette liste conserve un panier d'éléments et plusieurs suppressions plantent le site. Quelle est la meilleure méthode pour les synchroniser? Un verrou suffit-il? L'option de verrouillage semble être laide parce que le code est réparti partout et est assez salissant.Comment synchroniser l'accès à une liste <T> utilisé dans ASP.NET?

Mise à jour: Voici une liste mis en œuvre comme ceci: public class MyList: Liste < SomeCustomType> {}

Ceci est un site existant afin de ne pas tant de modifications sont autorisées à lui. Comment est-ce que je devrais refactoriser ceci afin de me verrouiller en toute sécurité lors de l'itération?

aucune idée! L'utilisation de lock est la manière correcte de synchroniser l'accès aux collections génériques.

Répondre

7

Est-ce que cette liste en mémoire est partagée entre les demandes? Cela ressemble à une cause potentielle de problèmes, même lorsque vous avez le verrouillage. Les collections partagées mutables doivent généralement être évitées, IME. Notez que si vous décidez de vous synchroniser, vous devrez verrouiller la liste pendant toute la durée de l'itération et d'autres opérations composées. Juste le verrouillage sur chaque accès spécifique n'est pas assez bon.

4

l'accès à la collection est réparti partout, vous pouvez créer une classe wrapper pour envelopper l'accès à la collection de sorte que la surface d'interaction est réduite. Vous pouvez ensuite introduire la synchronisation dans l'objet d'emballage.

4

Oui, vous avez à l'aide List.SyncRoot comme lock-ce:

lock (myList.SyncRoot) 
{ 
    // Access the collection. 
} 
+0

En fait, cela ne fonctionne pas car List <> implémente ICollection.SyncRoot explicitement. Pour l'utiliser, vous devez d'abord lancer myList à ICollection par exemple: lock ((ICollection) myList) .SyncRoot) {...} – user430788

0

@Samuel C'est exactement le point: vous ne pouvez pas corriger un problème comme celui-ci tout en modifiant la classe existante. Ne vous méprenez pas sur le class extends List<T>, qui, en accord avec MS ways, évite surtout les méthodes virtuelles (ce n'est que là où MS veut le faire qu'il le rend virtuel, ce qui est logique mais peut rendre la vie plus difficile dans les situations où vous avez besoin d'un hack). Même s'il a intégré le List<T> et que tout le code d'accès a été modifié, vous ne pouvez pas simplement modifier ce code en ajoutant des verrous pour résoudre le problème.

Pourquoi? Eh bien, les itérateurs pour une chose. Une collection ne peut simplement pas être modifiée entre chaque accès à la collection. Il ne peut pas être modifié pendant toute l'énumération.

En réalité, la synchronisation est complexe et dépend de la nature de la liste et du type de choses qui doivent être vraies en tout temps dans un système. Pour illustrer, voici un hack abusif IDisposable. Cette incorpore la liste et redécouvre toutes les fonctionnalités de la liste qui est utilisée quelque part, de sorte que l'accès à la liste peut être synchronisé. En outre, il ne pas implémenter IEnumerable. Au lieu de cela, la seule façon d'obtenir l'accès à enumerate sur la liste est via une méthode qui retourne un type jetable. Ce type entre dans le moniteur lorsqu'il est créé et le quitte à nouveau lorsqu'il est éliminé. Cela garantit que la liste n'est pas accessible pendant l'itération. Cependant, ce n'est pas encore assez, comme mon exemple l'illustrera.

D'abord la liste piraté:

public class MyCollection { object syncRoot = new object(); List list = new List();

public void Add(T item) { lock (syncRoot) list.Add(item); } 

public int Count 
{ 
    get { lock (syncRoot) return list.Count; } 
} 

public IteratorWrapper GetIteratorWrapper() 
{ 
    return new IteratorWrapper(this); 
} 


public class IteratorWrapper : IDisposable, IEnumerable<T> 
{ 
    bool disposed; 
    MyCollection<T> c; 
    public IteratorWrapper(MyCollection<T> c) { this.c = c; Monitor.Enter(c.syncRoot); } 
    public void Dispose() { if (!disposed) Monitor.Exit(c.syncRoot); disposed = true; } 

    public IEnumerator<T> GetEnumerator() 
    { 
     return c.list.GetEnumerator(); 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

}

Ensuite, une application console utilisant:

class Program { static MyCollection strings = new MyCollection();

static void Main(string[] args) 
{ 
    new Thread(adder).Start(); 
    Thread.Sleep(15); 
    dump(); 
    Thread.Sleep(125); 
    dump(); 
    Console.WriteLine("Press any key."); 
    Console.ReadKey(true); 
} 

static void dump() 
{ 
    Console.WriteLine(string.Format("Count={0}", strings.Count).PadLeft(40, '-')); 
    using (var enumerable = strings.GetIteratorWrapper()) 
    { 
     foreach (var s in enumerable) 
      Console.WriteLine(s); 
    } 
    Console.WriteLine("".PadLeft(40, '-')); 
} 

static void adder() 
{ 
    for (int i = 0; i < 100; i++) 
    { 
     strings.Add(Guid.NewGuid().ToString("N")); 
     Thread.Sleep(7); 
    } 
} 

}

Notez la méthode "décharge": Il accès comte, qui est de verrouillage absurdement la list dans une tentative pour le rendre "thread safe", puis itère à travers les éléments. Mais il existe une condition de concurrence entre le getter Count (qui verrouille, obtient le compte, puis libère) et l'instruction using. Donc, il ne peut pas vider le nombre d'éléments, il choses qu'il fait.

Ici, cela peut ne pas avoir d'importance. Mais si le code à la place a fait quelque chose comme:

var a = new string[strings.Count]; 
for (int i=0; i < strings.Count; i++) { ... } 

Ou encore plus susceptibles de gâcher les choses:

var n = strings.Count; 
var a = new string[n]; 
for (int i=0; i < n; i++) { ... } 

L'ancien va exploser si les articles sont simultanément ajoutés à la liste. Ce dernier ne va pas bien si les éléments sont supprimés de la liste. Et dans les deux cas, la sémantique du code peut ne pas être affectée par les modifications apportées à la liste, même si les modifications ne provoquent pas le blocage du code. Dans le premier cas, des éléments sont peut-être supprimés de la liste, ce qui entraîne le remplissage du tableau. Par la suite, quelque chose de très éloigné dans le code tombe en panne à cause des valeurs nulles dans le tableau. La leçon à tirer de ceci est: L'état partagé peut être très difficile. Vous avez besoin d'un plan plan et vous devez avoir une stratégie pour vous assurer que la signification est bonne.

Dans chacun de ces cas, le bon fonctionnement ne serait possible qu'en s'assurant que le verrou couvre toutes les opérations connexes. Il pourrait y avoir beaucoup d'autres déclarations entre et le verrou pourrait couvrir de nombreux appels de méthode et/ou impliquer de nombreux objets différents. Il n'y a pas de solution miracle qui permet de corriger ces problèmes simplement en modifiant la collection elle-même, car la synchronisation correcte dépend de ce que la collection signifie. Quelque chose comme un cache d'objets en lecture seule peut probablement être synchronisé uniquement par rapport à add/remove/lookup, mais si la collection elle-même est censée représenter un concept significatif/important, cela ne sera jamais suffisant.

Questions connexes