2009-02-09 9 views

Antwort

31

Das Problem ist das Entwerfen einer Thread-sicheren Sammlung ist nicht einfach. Sicher ist es einfach genug, um eine Sammlung zu entwerfen, die von mehreren Threads geändert werden kann, ohne den Zustand zu beschädigen. Es ist jedoch viel schwieriger, eine Sammlung zu erstellen, die verwendbar ist, da sie aus mehreren Threads aktualisiert wird. Nehmen Sie den folgenden Code als Beispiel.

Nehmen wir an, dass myCollection eine threadsichere Sammlung ist, bei der das Hinzufügen und Aktualisieren garantiert den Zustand nicht beschädigt. Dieser Code ist nicht Thread-sicher und ist eine Race-Bedingung.

Warum? Obwohl myCollection sicher ist, gibt es keine Garantie dafür, dass zwischen den beiden Methodenaufrufen von myCollection keine Änderung stattfindet: named Count und Indexer. Ein anderer Thread kann kommen und alle Elemente zwischen diesen Aufrufen entfernen.

Diese Art von Problem macht die Verwendung einer Sammlung dieser Art ganz ehrlich ein Albtraum. Sie können nicht zulassen, dass der Rückgabewert eines Aufrufs einen nachfolgenden Aufruf der Sammlung beeinflusst.

EDIT

erweiterte ich diese Diskussion auf einem aktuellen Blog-Beitrag: http://blogs.msdn.com/jaredpar/archive/2009/02/11/why-are-thread-safe-collections-so-hard.aspx

+0

Ich habe genau das gleiche Problem. Wie verwende ich den Dispatcher oder sonst ein Element zu meiner BindingList von einem Hintergrundthread hinzufügen? – Houman

+0

Ja, kurz gesagt, das Design der .NET-Sammlung * Schnittstellen * selbst ist nicht geeignet für Thread-Sicherheit. Wie Jared beispielsweise ausführt, ist die Count-Eigenschaft in einer Multithread-Umgebung nutzlos. –

+0

Es gibt viele nützliche Teilmengen der 'IList ' Funktionalität, die threadsicher implementiert werden können. Die Elemente, die Dinge entfernen oder Dinge bewegen, wären problematisch, aber der Rest der Schnittstelle wäre eine nützliche Untermenge für viele Anwendungen. Eine Version von 'Add', die den Index des hinzugefügten Elements angibt, wäre hilfreich, aber nicht alle Anwendungen würden es benötigen. Wenn jedes Element, das jemals hinzugefügt wurde, im selben Slot für die gesamte Lebensdauer der Liste vorhanden ist, kann eine threadsichere IList Implementierung in vielen Multithread-Szenarien ohne externe Sperrung nützlich sein. – supercat

6

Um ein wenig zu Jareds ausgezeichnete Antwort hinzu: Thread-Sicherheit kostenlos nicht kommt. Viele (die meisten?) Sammlungen werden nur innerhalb eines einzelnen Threads verwendet. Warum sollten diese Sammlungen Leistungs- oder Funktionalitätsstrafen haben, um mit dem Multi-Thread-Fall fertig zu werden?

+0

Nun, vielleicht sollte ich es dann umformulieren: Warum bietet das Framework keine ThreadSafeObservableCollection oder ähnliches? –

+0

Das ist eine vernünftige Frage - aber dann kommt Jareds Antwort ins Spiel. Es hängt wirklich davon ab, was Sie mit "thread-safe" meinen - was kein einfaches Ja/Nein-Flag ist. –

2

Wenn Sie verrückt werden möchten - here's ein ThreadedBindingList<T>, die Benachrichtigungen automatisch auf dem UI-Thread zurück. Es wäre jedoch immer noch sicher, dass ein Thread Aktualisierungen usw. gleichzeitig durchführt.

+0

Diese Implementierung fügt dem Sync-Kontext-Thread nur "Adds" hinzu: schützt nicht vor einer Reihe anderer Race-Bedingungen und/oder Sammlungs-modifizierter Fehler beim Auflisten der Liste. – piers7

5

Ideen von allen anderen Antworten Sammeln, ich denke, das ist der einfachste Weg ist, Ihre Probleme zu lösen:

Ändern Sie Ihre Frage: „Warum ist nicht Klasse X sane“

zu

"Was ist der vernünftige Weg, dies X mit Klasse zu tun?"

  1. in Konstruktor Ihrer Klasse, den aktuellen displatcher wie Sie Ihre beobachtbare Sammlungen zu erstellen. Becuase, wie Sie darauf hingewiesen, muss die Änderung auf der ursprünglichen Thread durchgeführt werden, die nicht die Haupt GUI-Thread sein kann. So App.Current.Dispatcher ist nicht immer richtig, und nicht alle Klassen haben eine this.Dispatcher.

    _dispatcher = System.Windows.Threading.Dispatcher.CurrentDispatcher; 
    _data = new ObservableCollection<MyDataItemClass>(); 
    
  2. den Dispatcher Verwenden Sie Ihre Codeabschnitte , die den ursprünglichen Thread müssen aufzurufen.

    _dispatcher.Invoke(new Action(() => { _data.Add(dataItem); })); 
    

, die den Trick für Sie tun sollten. Es gibt Situationen, die Sie vielleicht bevorzugen . BeginInvoke anstelle von . Invoice.