2008-10-09 8 views
5

Ich habe einige Probleme auf einer Website mit dem gleichzeitigen Zugriff auf eine Liste. Diese Liste enthält einen Warenkorb mit Artikeln und mehrere Löschvorgänge führen zum Absturz der Site. Welches ist die beste Methode, um sie zu synchronisieren? Ist eine Sperre ausreichend? Die Lock-Option scheint hässlich zu sein, weil der Code überall verteilt ist und ziemlich unordentlich ist.Wie synchronisiert man den Zugriff auf eine in ASP.NET verwendete Liste <T>?

Update: Dies ist eine Liste wie folgt umgesetzt: public class MyList: Liste < SomeCustomType> {}

Dies ist ein Vermächtnis Website so nicht so viele Änderungen daran sind erlaubt. Wie sollte ich dies umgestalten, um sicher zu sperren, wenn ich darüber iteriere?

keine Idee!

Antwort

7

Also ist diese In-Memory-Liste zwischen Anfragen geteilt? Das klingt nach einer möglichen Ursache für Probleme, selbst wenn Sie eine Sperre haben. Veränderbare gemeinsame Sammlungen sollten im Allgemeinen vermieden werden, IME.

Beachten Sie, dass wenn Sie entscheiden, zu synchronisieren, müssen Sie Dinge tun, wie die Liste für den gesamten Verlauf der Iteration über es und andere zusammengesetzte Operationen sperren. Es ist nicht gut genug, sich auf jeden spezifischen Zugriff zu beschränken.

4

Die Verwendung der Sperre ist der richtige Weg, um den Zugriff auf generische Sammlungen zu synchronisieren. Der Zugriff auf die Sammlung ist überall verteilt. Sie können eine Wrapperklasse erstellen, um den Zugriff auf die Sammlung zu ermöglichen, sodass die Interaktionsfläche reduziert wird. Sie können dann die Synchronisation innerhalb des Wrapping-Objekts einführen.

4

Ja, müssen Sie dies mit List.SyncRoot wie Lock-:

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

Eigentlich funktioniert das nicht, da List <> ICollection.SyncRoot explizit implementiert. Um es zu verwenden, müssen Sie zunächst myList auf eine ICollection z. B .: Sperre ((ICollection) myList) .SyncRoot) {...} – user430788

0

@Samuel Das ist genau der Punkt: Es ist nicht ein Problem wie dieses nur durch die Änderung der bestehenden Klasse korrigieren. Vergiss nicht, dass die class extends List<T>, die im Einklang mit MS-Methoden in der Regel vermeidet virtuelle Methoden (nur wo MS uns übersteuern wollen machen sie es virtuell, was meistens Sinn macht aber das Leben schwieriger in Situationen, in denen Sie brauchen einen Hack). Selbst wenn es die List<T> einbettete und der gesamte Zugriff auf Code durchging, den Sie ändern können, können Sie diesen Code nicht einfach ändern, indem Sie Sperren hinzufügen, um das Problem zu lösen.

Warum? Nun, Iteratoren für eine Sache. Eine Sammlung kann nicht einfach zwischen jedem Zugriff auf die Sammlung geändert werden. Sie kann während der gesamten Aufzählung nicht geändert werden.

In Wirklichkeit ist Synchronisation eine komplexe Angelegenheit und hängt davon ab, was die Liste wirklich ist und welche Dinge in einem System zu jeder Zeit wahr sein müssen.

Um zu veranschaulichen, hier ist ein Hack Missbrauch IDisposable. Diese bettet die Liste und deklariert die Funktionalität der Liste, die irgendwo verwendet wird, so dass der Zugriff auf die Liste synchronisiert werden kann. Darüber hinaus ist es nicht implementieren IEnumerable. Stattdessen besteht die einzige Möglichkeit, über die Liste aufzulisten, über eine Methode, die einen Disposable-Typ zurückgibt. Dieser Typ tritt bei der Erstellung in den Monitor ein und bei der Entsorgung wieder aus. Dies stellt sicher, dass die Liste während der Iteration nicht zugänglich ist. Dies ist jedoch immer noch nicht wirklich genug, wie meine Beispielanwendung zeigt.

Zuerst wird die gehackt Liste:

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(); 
    } 
} 

}

Dann wird eine Konsole app es:

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); 
    } 
} 

}

Beachten Sie die "dump" Methode: Es Zugriffszählregister, die die meaninglessly Verriegeln Liste in einem Versuch, es "Thread-sicher" zu machen, und dann durch die Elemente iteriert. Aber es gibt eine Race-Bedingung zwischen dem Count-Getter (der sperrt, den Count bekommt, dann freigibt) und der using-Anweisung. Es kann also nicht die Anzahl der Items dumpen, die es tut.

Hier ist es egal. Aber was, wenn der Code stattdessen etwas tat, wie:

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

Oder noch wahrscheinlicher zu durcheinander zu bringen:

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

Erstere wird die Luft zu sprengen, wenn Einzelteile gleichzeitig in die Liste aufgenommen werden. Letzteres funktioniert nicht gut, wenn Elemente aus der Liste entfernt werden. Und in beiden Fällen kann die Semantik des Codes nicht durch Änderungen an der Liste nicht beeinträchtigt werden, auch wenn die Änderungen nicht zum Absturz des Codes führen. Im ersten Fall werden möglicherweise Elemente aus der Liste entfernt, wodurch das Array nicht gefüllt wird. In der Folge stürzt etwas weit Entferntes im Code ab, weil die Werte im Array Null sind.

Die Lektion, die daraus zu ziehen ist: Geteilter Zustand kann sehr schwierig sein. Sie brauchen einen Upfront Plan und Sie müssen eine Strategie haben, wie Sie sicherstellen, dass die Bedeutung richtig ist.

In jedem dieser Fälle würde ein korrekter Betrieb nur erreicht werden, wenn sichergestellt wird, dass das Schloss alle damit verbundenen Vorgänge umfasst. Es könnte viele andere Anweisungen dazwischen geben, und die Sperre könnte viele Methodenaufrufe umfassen und/oder viele verschiedene Objekte beinhalten. Es gibt kein Wundermittel, das es ermöglicht, diese Probleme zu beheben, indem man lediglich die Sammlung selbst ändert, denn die korrekte Synchronisation hängt davon ab, was die Sammlung bedeutet. So etwas wie ein Cache von schreibgeschützten Objekten kann wahrscheinlich nur in Bezug auf add/remove/lookup synchronisiert werden, aber wenn die Sammlung selbst ein sinnvolles/wichtiges Konzept darstellen soll, wird dies niemals ausreichen.