Estou usando ConcurrentQueue
para uma estrutura de dados compartilhada cujo propósito é manter os últimos N objetos passados para ela (tipo de histórico).
Suponha que temos um navegador e queremos os últimos 100 URLs navegados. Eu quero uma fila que descarta (desenfileira) automaticamente a entrada mais antiga (primeira) na inserção de uma nova entrada (enfileirar) quando a capacidade ficar cheia (100 endereços no histórico).
Como posso fazer isso usando System.Collections
?
Respostas:
Eu escreveria uma classe de wrapper que, no Enqueue, verificaria a contagem e, em seguida, removeria da fila quando a contagem ultrapassar o limite.
fonte
q
é privado para o objeto, de modo quelock
irá impedir que outros threads acessem simultaneamente.Count
eTryDequeue
são duas operações independentes que não são sincronizadas pelo BCL Simultâneo.ConcurrentQueue<T>
objeto por umQueue<T>
objeto mais leve.Enqueue
, ainda chamarão a fila original. Em outras palavras, embora essa resposta seja marcada como aceita, ela está completa e totalmente inválida.Eu escolheria uma pequena variante ... estender ConcurrentQueue para poder usar extensões Linq em FixedSizeQueue
fonte
Para quem o achar útil, aqui está um código de trabalho baseado na resposta de Richard Schneider acima:
fonte
Para saber o que vale, aqui está um buffer circular leve com alguns métodos marcados para uso seguro e não seguro.
Gosto de usar a
Foo()/SafeFoo()/UnsafeFoo()
convenção:Foo
métodos chamamUnsafeFoo
como padrão.UnsafeFoo
métodos modificam o estado livremente sem um bloqueio, eles devem apenas chamar outros métodos não seguros.SafeFoo
métodos chamamUnsafeFoo
métodos dentro de um bloqueio.É um pouco prolixo, mas comete erros óbvios, como chamar métodos inseguros fora de um bloqueio em um método que deveria ser thread-safe, mais aparentes.
fonte
Aqui está minha opinião sobre a fila de tamanho fixo
Ele usa a fila regular, para evitar a sobrecarga de sincronização quando a
Count
propriedade é usadaConcurrentQueue
. Ele também implementaIReadOnlyCollection
para que os métodos LINQ possam ser usados. O resto é muito semelhante às outras respostas aqui.fonte
Apenas por diversão, aqui está outra implementação que, acredito, aborda a maioria das preocupações dos comentadores. Em particular, thread-safety é alcançada sem bloqueio e a implementação é oculta pela classe de empacotamento.
fonte
_queue.Enqueue(obj)
mas antesInterlocked.Increment(ref _count)
, e as outras chamadas de thread.Count
? Seria uma contagem errada. Não verifiquei os outros problemas.Minha versão é apenas uma subclasse das normais
Queue
.. nada de especial, mas vendo todos participando e ainda acompanha o título do tópico, posso muito bem colocá-lo aqui. Ele também retorna os desenfileirados apenas no caso.fonte
Vamos adicionar mais uma resposta. Por que isso em vez de outros?
1) Simplicidade. Tentar garantir que o tamanho é bom e bom, mas leva a uma complexidade desnecessária que pode apresentar seus próprios problemas.
2) Implementa IReadOnlyCollection, o que significa que você pode usar o Linq nele e passá-lo para uma variedade de coisas que esperam o IEnumerable.
3) Sem bloqueio. Muitas das soluções acima usam bloqueios, o que é incorreto em uma coleção sem bloqueio.
4) Implementa o mesmo conjunto de métodos, propriedades e interfaces que ConcurrentQueue faz, incluindo IProducerConsumerCollection, que é importante se você deseja usar a coleção com BlockingCollection.
Essa implementação pode potencialmente terminar com mais entradas do que o esperado se TryDequeue falhar, mas a frequência dessa ocorrência não parece valer um código especializado que inevitavelmente prejudicará o desempenho e causará seus próprios problemas inesperados.
Se você realmente deseja garantir um tamanho, implementar um Prune () ou método semelhante parece ser a melhor ideia. Você poderia usar um bloqueio de leitura ReaderWriterLockSlim nos outros métodos (incluindo TryDequeue) e obter um bloqueio de gravação apenas durante a remoção.
fonte
Só porque ninguém disse isso ainda ... você pode usar um
LinkedList<T>
e adicionar o thread de segurança:Uma coisa a ser observada é que a ordem de enumeração padrão será LIFO neste exemplo. Mas isso pode ser substituído, se necessário.
fonte
Para seu prazer de codificação, apresento a você o '
ConcurrentDeck
'Exemplo de uso:
fonte
Bem, depende do uso que eu percebi que algumas das soluções acima podem exceder o tamanho quando usadas em um ambiente multip-threaded. De qualquer forma, meu caso de uso era exibir os últimos 5 eventos e há vários threads gravando eventos na fila e um outro thread lendo a partir dele e exibindo-os em um controle Winform. Então essa foi a minha solução.
EDITAR: Como já usamos o bloqueio em nossa implementação, não precisamos realmente do ConcurrentQueue, ele pode melhorar o desempenho.
EDIT: Nós realmente não precisamos
syncObject
no exemplo acima e podemos preferir usar oqueue
objeto, uma vez que não estamos reinicializandoqueue
em nenhuma função e está marcado como dereadonly
qualquer maneira.fonte
A resposta aceita terá efeitos colaterais evitáveis.
Os links abaixo são referências que usei quando escrevi meu exemplo abaixo.
Embora a documentação da Microsoft seja um pouco enganosa, pois eles usam um bloqueio, eles bloqueiam as classes de segmento. As próprias classes de segmento usam Interlocked.
fonte
Aqui está outra implementação que usa o ConcurrentQueue subjacente tanto quanto possível, enquanto fornece as mesmas interfaces disponibilizadas por meio do ConcurrentQueue.
fonte
Esta é minha versão da fila:
Acho útil ter um construtor baseado em um IEnumerable e acho útil ter um GetSnapshot para ter uma lista segura multithread (array, neste caso) dos itens no momento da chamada, que não sobe erros se a coleção subjacente for alterada.
A verificação de contagem dupla serve para evitar o bloqueio em algumas circunstâncias.
fonte