Używam ConcurrentQueue
do udostępnionej struktury danych, której celem jest przechowywanie ostatnich N przekazanych do niej obiektów (rodzaj historii).
Załóżmy, że mamy przeglądarkę i chcemy mieć 100 ostatnio przeglądanych adresów URL. Chcę kolejki, która automatycznie upuszcza (usuwa z kolejki) najstarszy (pierwszy) wpis po wstawieniu nowego wpisu (kolejce), gdy pojemność się zapełni (100 adresów w historii).
Jak mogę to osiągnąć za pomocą System.Collections
?
Odpowiedzi:
Napisałbym klasę opakowującą, która w Enqueue sprawdzałaby Count, a następnie Dequeue, gdy liczba przekroczy limit.
źródło
q
jest prywatny dla obiektu, więclock
uniemożliwi równoczesny dostęp innym wątkom.Count
iTryDequeue
są to dwie niezależne operacje, które nie są synchronizowane przez BCL Concurrent.ConcurrentQueue<T>
obiektu naQueue<T>
obiekt, który jest lżejszy.Enqueue
nadal będzie wywoływać oryginalną kolejkę. Innymi słowy, chociaż ta odpowiedź jest oznaczona jako zaakceptowana, jest całkowicie i całkowicie złamana.Wybrałbym niewielki wariant ... rozszerzyć ConcurrentQueue, aby móc używać rozszerzeń Linq w FixedSizeQueue
źródło
Dla każdego, kto uzna to za przydatne, oto działający kod oparty na odpowiedzi Richarda Schneidera powyżej:
źródło
Oto lekki bufor kołowy z niektórymi metodami oznaczonymi jako bezpieczne i niebezpieczne.
Lubię korzystać z
Foo()/SafeFoo()/UnsafeFoo()
konwencji:Foo
metody wywoływaneUnsafeFoo
jako domyślne.UnsafeFoo
metody swobodnie modyfikują stan bez blokady, powinny wywoływać tylko inne niebezpieczne metody.SafeFoo
metody wywołująUnsafeFoo
metody wewnątrz blokady.Jest to trochę rozwlekłe, ale powoduje oczywiste błędy, takie jak wywoływanie niebezpiecznych metod poza blokadą w metodzie, która ma być bezpieczna dla wątków, bardziej widoczna.
źródło
Oto moje spojrzenie na kolejkę o stałym rozmiarze
Używa zwykłej kolejki, aby uniknąć obciążenia synchronizacji, gdy
Count
właściwość jest używanaConcurrentQueue
. Implementuje również,IReadOnlyCollection
dzięki czemu można używać metod LINQ. Reszta jest bardzo podobna do innych odpowiedzi tutaj.źródło
Dla zabawy, oto kolejna implementacja, która moim zdaniem rozwiązuje większość obaw komentujących. W szczególności bezpieczeństwo gwintów uzyskuje się bez blokowania, a implementacja jest ukryta przez klasę zawijania.
źródło
_queue.Enqueue(obj)
ale przedInterlocked.Increment(ref _count)
, a inne wywołania wątku.Count
? Wynik byłby błędny. Nie sprawdziłem innych problemów.Moja wersja jest po prostu podklasą normalnych
Queue
... nic specjalnego, ale widząc wszystkich uczestniczących i nadal jest zgodna z tytułem tematu, równie dobrze mógłbym to tutaj umieścić. Na wszelki wypadek zwraca również te usunięte z kolejki.źródło
Dodajmy jeszcze jedną odpowiedź. Dlaczego to ponad innymi?
1) Prostota. Próba zagwarantowania, że rozmiar jest dobry i dobry, ale prowadzi do niepotrzebnej złożoności, która może powodować własne problemy.
2) Implementuje IReadOnlyCollection, co oznacza, że możesz użyć Linq na nim i przekazać go do różnych rzeczy, które oczekują IEnumerable.
3) Bez blokowania. Wiele z powyższych rozwiązań wykorzystuje zamki, co jest nieprawidłowe w przypadku kolekcji bez zamków.
4) Implementuje ten sam zestaw metod, właściwości i interfejsów, co ConcurrentQueue, w tym IProducerConsumerCollection, co jest ważne, jeśli chcesz używać kolekcji z BlockingCollection.
Ta implementacja może potencjalnie zakończyć się większą liczbą wpisów niż oczekiwano, jeśli TryDequeue zawiedzie, ale częstotliwość tego nie wydaje się warta wyspecjalizowanego kodu, który nieuchronnie obniży wydajność i spowoduje własne nieoczekiwane problemy.
Jeśli absolutnie chcesz zagwarantować rozmiar, zaimplementowanie metody Prune () lub podobnej wydaje się najlepszym pomysłem. Możesz użyć blokady odczytu ReaderWriterLockSlim w innych metodach (w tym TryDequeue) i przyjąć blokadę zapisu tylko podczas czyszczenia.
źródło
Tylko dlatego, że nikt jeszcze tego nie powiedział ... możesz użyć
LinkedList<T>
ai dodać zabezpieczenie wątku:Należy zwrócić uwagę na to, że w tym przykładzie domyślną kolejnością wyliczania będzie LIFO. Ale w razie potrzeby można to zmienić.
źródło
Dla przyjemności z kodowania przekazuję Ci '
ConcurrentDeck
'Przykładowe zastosowanie:
źródło
Cóż, zależy to od zastosowania, które zauważyłem, że niektóre z powyższych rozwiązań mogą przekraczać rozmiar, gdy są używane w środowisku wielowątkowym. W każdym razie moim przypadkiem użycia było wyświetlenie ostatnich 5 zdarzeń i istnieje wiele wątków zapisujących zdarzenia w kolejce i jeden inny wątek odczytujący z niej i wyświetlający go w kontrolce Winform. Więc to było moje rozwiązanie.
EDYCJA: Ponieważ używamy już blokowania w naszej implementacji, tak naprawdę nie potrzebujemy ConcurrentQueue, może to poprawić wydajność.
EDYCJA: Naprawdę nie potrzebujemy
syncObject
w powyższym przykładzie i możemy raczej użyćqueue
obiektu, ponieważ nie jesteśmy ponownie inicjalizowaniqueue
w żadnej funkcji i jest oznaczony jakoreadonly
mimo wszystko.źródło
Zaakceptowana odpowiedź będzie miała możliwe do uniknięcia skutki uboczne.
Poniższe linki to odniesienia, których użyłem, kiedy pisałem mój przykład poniżej.
Chociaż dokumentacja firmy Microsoft jest nieco myląca, ponieważ używają blokady, blokują jednak klasy segregacji. Same klasy segmentów używają Interlocked.
źródło
Oto kolejna implementacja, która wykorzystuje bazową ConcurrentQueue w jak największym stopniu, zapewniając jednocześnie te same interfejsy, które są dostępne za pośrednictwem ConcurrentQueue.
źródło
Oto moja wersja kolejki:
Uważam, że przydatne jest posiadanie konstruktora, który jest zbudowany na IEnumerable i uważam, że przydatne jest posiadanie GetSnapshot, aby mieć wielowątkową bezpieczną listę (w tym przypadku tablicę) elementów w momencie wywołania, która nie rośnie błędy, jeśli zmieni się kolekcja podkładów.
Podwójna kontrola liczenia ma na celu zapobieżenie blokadzie w niektórych okolicznościach.
źródło