Ich habe ein Szenario, in dem mehrere Threads zu einer Warteschlange hinzugefügt werden und mehrere Threads aus derselben Warteschlange lesen. Wenn die Warteschlange eine bestimmte Größe erreicht, werden alle Threads , die die Warteschlange füllen, beim Hinzufügen blockiert, bis ein Element aus der Warteschlange entfernt wird.
Die folgende Lösung verwende ich gerade und meine Frage lautet: Wie kann dies verbessert werden? Gibt es ein Objekt, das dieses Verhalten bereits in der BCL aktiviert, das ich verwenden sollte?
internal class BlockingCollection<T> : CollectionBase, IEnumerable
{
//todo: might be worth changing this into a proper QUEUE
private AutoResetEvent _FullEvent = new AutoResetEvent(false);
internal T this[int i]
{
get { return (T) List[i]; }
}
private int _MaxSize;
internal int MaxSize
{
get { return _MaxSize; }
set
{
_MaxSize = value;
checkSize();
}
}
internal BlockingCollection(int maxSize)
{
MaxSize = maxSize;
}
internal void Add(T item)
{
Trace.WriteLine(string.Format("BlockingCollection add waiting: {0}", Thread.CurrentThread.ManagedThreadId));
_FullEvent.WaitOne();
List.Add(item);
Trace.WriteLine(string.Format("BlockingCollection item added: {0}", Thread.CurrentThread.ManagedThreadId));
checkSize();
}
internal void Remove(T item)
{
lock (List)
{
List.Remove(item);
}
Trace.WriteLine(string.Format("BlockingCollection item removed: {0}", Thread.CurrentThread.ManagedThreadId));
}
protected override void OnRemoveComplete(int index, object value)
{
checkSize();
base.OnRemoveComplete(index, value);
}
internal new IEnumerator GetEnumerator()
{
return List.GetEnumerator();
}
private void checkSize()
{
if (Count < MaxSize)
{
Trace.WriteLine(string.Format("BlockingCollection FullEvent set: {0}", Thread.CurrentThread.ManagedThreadId));
_FullEvent.Set();
}
else
{
Trace.WriteLine(string.Format("BlockingCollection FullEvent reset: {0}", Thread.CurrentThread.ManagedThreadId));
_FullEvent.Reset();
}
}
}
c#
.net
multithreading
collections
queue
Eric Schoonover
quelle
quelle
Antworten:
Das sieht sehr unsicher aus (sehr wenig Synchronisation); wie wäre es mit so etwas wie:
(bearbeiten)
In Wirklichkeit möchten Sie die Warteschlange schließen, damit die Leser sauber beendet werden - vielleicht so etwas wie ein Bool-Flag -, wenn sie gesetzt ist, wird eine leere Warteschlange nur zurückgegeben (anstatt zu blockieren):
quelle
Wait
, damit andere Threads sie erwerben können. Es wacht die Sperre (n) zurück, wenn es aufwacht.Verwenden Sie .net 4 BlockingCollection, um Add () in die Warteschlange zu stellen und Take () aus der Warteschlange zu entfernen. Es verwendet intern nicht blockierende ConcurrentQueue. Weitere Informationen hier Schnelle und beste Warteschlangentechnik für Produzenten und Konsumenten BlockingCollection vs Concurrent Queue
quelle
"Wie kann das verbessert werden?"
Nun, Sie müssen sich jede Methode in Ihrer Klasse ansehen und überlegen, was passieren würde, wenn ein anderer Thread gleichzeitig diese Methode oder eine andere Methode aufruft. Beispielsweise setzen Sie eine Sperre für die Remove-Methode, nicht jedoch für die Add-Methode. Was passiert, wenn ein Thread gleichzeitig mit einem anderen Thread hinzugefügt wird? Schlechte Dinge.
Beachten Sie auch, dass eine Methode ein zweites Objekt zurückgeben kann, das Zugriff auf die internen Daten des ersten Objekts bietet, z. B. GetEnumerator. Stellen Sie sich vor, ein Thread durchläuft diesen Enumerator, ein anderer Thread ändert gleichzeitig die Liste. Nicht gut.
Eine gute Faustregel ist, dies zu vereinfachen, indem die Anzahl der Methoden in der Klasse auf das absolute Minimum reduziert wird.
Erben Sie insbesondere keine andere Containerklasse, da Sie alle Methoden dieser Klasse verfügbar machen und dem Aufrufer die Möglichkeit bieten, die internen Daten zu beschädigen oder teilweise vollständige Änderungen an den Daten anzuzeigen (genauso schlecht, weil die Daten erscheint in diesem Moment beschädigt). Verstecken Sie alle Details und seien Sie völlig rücksichtslos darüber, wie Sie den Zugriff darauf erlauben.
Ich würde Ihnen dringend empfehlen, Standardlösungen zu verwenden - ein Buch über Threading zu erhalten oder eine Bibliothek von Drittanbietern zu verwenden. Andernfalls werden Sie angesichts des Versuchs Ihren Code für eine lange Zeit debuggen.
Wäre es für Remove nicht sinnvoller, ein Element zurückzugeben (z. B. das zuerst hinzugefügte, da es sich um eine Warteschlange handelt), als dass der Anrufer ein bestimmtes Element auswählt? Und wenn die Warteschlange leer ist, sollte Entfernen möglicherweise auch blockieren.
Update: Marc's Antwort setzt tatsächlich all diese Vorschläge um! :) Aber ich lasse das hier, da es hilfreich sein kann zu verstehen, warum seine Version eine solche Verbesserung darstellt.
quelle
Sie können BlockingCollection und ConcurrentQueue im System.Collections.Concurrent-Namespace verwenden
quelle
Ich habe das gerade mit den Reactive Extensions aufgerissen und mich an diese Frage erinnert:
Nicht unbedingt ganz sicher, aber sehr einfach.
quelle
Dies ist, was ich für eine Thread-sichere begrenzte Blockierungswarteschlange op kam.
quelle
Ich habe die TPL noch nicht vollständig erforscht, aber sie haben möglicherweise etwas, das Ihren Anforderungen entspricht, oder zumindest ein Reflektorfutter, von dem Sie sich inspirieren lassen können.
Hoffentlich hilft das.
quelle
Nun, Sie könnten sich die
System.Threading.Semaphore
Klasse ansehen . Davon abgesehen - nein, du musst das selbst machen. AFAIK gibt es keine solche eingebaute Sammlung.quelle
Wenn Sie einen maximalen Durchsatz wünschen, bei dem mehrere Leser lesen und nur ein Schreiber schreiben kann, bietet BCL ReaderWriterLockSlim, mit dem Sie Ihren Code verkleinern können ...
quelle