Eu tenho um cenário em que vários threads são adicionados a uma fila e vários threads são lidos na mesma fila. Se a fila atingir um tamanho específico, todos os segmentos que estão preenchendo a fila serão bloqueados na adição até que um item seja removido da fila.
A solução abaixo é o que estou usando agora e minha pergunta é: como isso pode ser melhorado? Existe um objeto que já habilite esse comportamento na BCL que eu deveria estar usando?
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
fonte
fonte
Respostas:
Isso parece muito inseguro (muito pouca sincronização); que tal algo como:
(editar)
Na realidade, você desejaria uma maneira de fechar a fila para que os leitores comecem a sair de maneira limpa - talvez algo como um sinalizador bool - se configurada, uma fila vazia retornará (em vez de bloquear):
fonte
Wait
, para que outros threads possam adquiri-lo. Recupera o (s) bloqueio (s) quando acorda.Use .net 4 BlockingCollection, para enfileirar, use Add (), para desenfileirar, use Take (). Ele usa internamente ConcurrentQueue sem bloqueio. Mais informações aqui Técnica rápida e melhor da fila Produtor / consumidor BlockingCollection x fila simultânea
fonte
"Como isso pode ser melhorado?"
Bem, você precisa examinar todos os métodos da sua classe e considerar o que aconteceria se outro thread estivesse chamando esse método ou qualquer outro método simultaneamente. Por exemplo, você coloca um bloqueio no método Remove, mas não no método Add. O que acontece se um thread é adicionado ao mesmo tempo que outro thread removido?Coisas ruins.
Considere também que um método pode retornar um segundo objeto que fornece acesso aos dados internos do primeiro objeto - por exemplo, GetEnumerator. Imagine que um segmento está passando por esse enumerador, outro segmento está modificando a lista ao mesmo tempo. Não é bom.
Uma boa regra é simplificar o processo, reduzindo o número de métodos da classe ao mínimo absoluto.
Em particular, não herde outra classe de contêiner, porque você exporá todos os métodos dessa classe, fornecendo uma maneira de o chamador corromper os dados internos ou ver alterações parcialmente completas nos dados (tão ruins quanto os dados). aparece corrompido naquele momento). Esconda todos os detalhes e seja completamente implacável sobre como você permite o acesso a eles.
Eu recomendo fortemente que você use soluções disponíveis no mercado - obtenha um livro sobre encadeamento ou use uma biblioteca de terceiros. Caso contrário, dado o que você está tentando, você estará depurando seu código por um longo tempo.
Além disso, não faria mais sentido remover remover um item (por exemplo, o que foi adicionado primeiro, por ser uma fila), em vez de o chamador escolher um item específico? E quando a fila estiver vazia, talvez o Remove também deva bloquear.
Atualização: a resposta de Marc realmente implementa todas essas sugestões! :) Mas vou deixar isso aqui, pois pode ser útil entender por que a versão dele é uma melhoria.
fonte
Você pode usar o BlockingCollection e ConcurrentQueue no espaço para nome System.Collections.Concurrent
fonte
Acabei de terminar isso usando as extensões reativas e lembrei-me desta pergunta:
Não necessariamente totalmente seguro, mas muito simples.
fonte
Foi isso que optei por uma fila de bloqueio limitada por thread.
fonte
Eu não explorei completamente o TPL, mas eles podem ter algo que atenda às suas necessidades, ou pelo menos alguma forragem do Reflector para obter alguma inspiração.
Espero que ajude.
fonte
Bem, você pode olhar para a
System.Threading.Semaphore
aula. Fora isso - não, você tem que fazer isso sozinho. AFAIK não existe essa coleção interna.fonte
Se você deseja um rendimento máximo, permitindo que vários leitores leiam e apenas um escritor escreva, o BCL possui algo chamado ReaderWriterLockSlim que deve ajudar a diminuir o seu código ...
fonte