Mam scenariusz, w którym mam wiele wątków dodających do kolejki i wiele wątków odczytujących z tej samej kolejki. Jeśli kolejka osiągnie określony rozmiar, wszystkie wątki , które wypełniają kolejkę, zostaną zablokowane przy dodawaniu, dopóki element nie zostanie usunięty z kolejki.
Poniższe rozwiązanie jest tym, czego teraz używam, a moje pytanie brzmi: Jak można to poprawić? Czy istnieje obiekt, który już włącza to zachowanie w BCL, którego powinienem używać?
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
źródło
źródło
Odpowiedzi:
Wygląda to bardzo niebezpiecznie (bardzo mała synchronizacja); co powiesz na coś takiego:
(edytować)
W rzeczywistości chciałbyś mieć sposób na zamknięcie kolejki, aby czytelnicy zaczęli czysto wychodzić - być może coś w rodzaju flagi bool - jeśli jest ustawiona, pusta kolejka po prostu wraca (zamiast blokować):
źródło
Wait
, aby inne wątki mogły ją zdobyć. Po przebudzeniu odzyskuje blokadę (i).Użyj .net 4 BlockingCollection, aby umieścić w kolejce użyj Add (), aby usunąć z kolejki użyj Take (). Wewnętrznie używa nieblokującej ConcurrentQueue. Więcej informacji tutaj Szybka i najlepsza technika kolejkowania producenta / konsumenta BlockingCollection vs Concurrent Queue
źródło
„Jak można to poprawić?”
Cóż, musisz przyjrzeć się każdej metodzie w swojej klasie i zastanowić się, co by się stało, gdyby inny wątek jednocześnie wywoływał tę metodę lub inną metodę. Na przykład blokada jest umieszczana w metodzie Remove, ale nie w metodzie Add. Co się stanie, jeśli jeden wątek zostanie dodany w tym samym czasie co inny wątek zostanie usunięty? Złe rzeczy.
Weź również pod uwagę, że metoda może zwrócić drugi obiekt, który zapewnia dostęp do wewnętrznych danych pierwszego obiektu - na przykład GetEnumerator. Wyobraź sobie, że jeden wątek przechodzi przez ten moduł wyliczający, a inny wątek modyfikuje listę w tym samym czasie. Niedobrze.
Dobrą praktyczną zasadą jest uproszczenie tego rozwiązania poprzez zmniejszenie liczby metod w klasie do absolutnego minimum.
W szczególności nie dziedzicz innej klasy kontenera, ponieważ ujawnisz wszystkie metody tej klasy, umożliwiając wywołującemu uszkodzenie wewnętrznych danych lub zobaczenie częściowo kompletnych zmian w danych (tak samo źle, ponieważ dane wydaje się w tym momencie uszkodzony). Ukryj wszystkie szczegóły i bądź całkowicie bezlitosny w kwestii tego, jak udzielasz do nich dostępu.
Zdecydowanie radzę korzystać z gotowych rozwiązań - zdobądź książkę o wątkach lub skorzystaj z biblioteki innej firmy. W przeciwnym razie, biorąc pod uwagę to, czego próbujesz, będziesz debugować swój kod przez długi czas.
Poza tym, czy nie miałoby większego sensu, gdyby funkcja Remove zwracała element (powiedzmy, ten, który został dodany jako pierwszy, ponieważ jest to kolejka), zamiast wybierania przez dzwoniącego określonego elementu? A kiedy kolejka jest pusta, być może Usuń również powinno się zablokować.
Aktualizacja: odpowiedź Marca faktycznie uwzględnia wszystkie te sugestie! :) Ale zostawię to tutaj, ponieważ może być pomocne zrozumienie, dlaczego jego wersja jest tak ulepszona.
źródło
Możesz użyć BlockingCollection i ConcurrentQueue w przestrzeni nazw System.Collections.Concurrent
źródło
Właśnie podrzuciłem to za pomocą Reactive Extensions i przypomniałem sobie to pytanie:
Niekoniecznie całkowicie bezpieczne, ale bardzo proste.
źródło
To jest to, co przyszedłem do bezpiecznej, ograniczonej kolejki blokowania wątków.
źródło
Nie do końca poznałem TPL, ale mogą mieć coś, co pasuje do twoich potrzeb lub przynajmniej trochę pożywienia Reflector, z którego można czerpać inspirację.
Mam nadzieję, że to pomoże.
źródło
Cóż, możesz spojrzeć na
System.Threading.Semaphore
klasę. Poza tym - nie, musisz to zrobić sam. AFAIK nie ma takiej wbudowanej kolekcji.źródło
Jeśli chcesz mieć maksymalną przepustowość, pozwalającą wielu czytelnikom na czytanie i tylko jednemu pisarzowi do pisania, BCL ma coś, co nazywa się ReaderWriterLockSlim, co powinno pomóc odchudzić twój kod ...
źródło