Estoy usando ConcurrentQueue
para una estructura de datos compartidos cuyo propósito es contener los últimos N objetos que se le pasaron (tipo de historial).
Supongamos que tenemos un navegador y queremos tener las últimas 100 URL examinadas. Quiero una cola que elimine automáticamente (retire) la entrada más antigua (primera) al insertar una nueva entrada (poner en cola) cuando se llene la capacidad (100 direcciones en el historial).
¿Cómo puedo lograr eso usando System.Collections
?
Respuestas:
Escribiría una clase contenedora que en Enqueue verificaría el Count y luego Dequeue cuando el conteo exceda el límite.
fuente
q
es privado para el objeto, por lo quelock
evitará que otros subprocesos tengan acceso simultáneo.Count
yTryDequeue
son dos operaciones independientes a las que no les importa sincronizar BCL Concurrent.ConcurrentQueue<T>
objeto por unQueue<T>
objeto que sea más liviano.Enqueue
aún llamará a la cola original. En otras palabras, aunque esta respuesta está marcada como aceptada, está completamente rota.Optaría por una ligera variante ... extender ConcurrentQueue para poder usar extensiones Linq en FixedSizeQueue
fuente
Para cualquiera que lo encuentre útil, aquí hay un código de trabajo basado en la respuesta de Richard Schneider anterior:
fuente
Por lo que vale, aquí hay un búfer circular liviano con algunos métodos marcados para uso seguro e inseguro.
Me gusta usar la
Foo()/SafeFoo()/UnsafeFoo()
convención:Foo
los métodos llamanUnsafeFoo
por defecto.UnsafeFoo
Los métodos modifican el estado libremente sin un bloqueo, solo deben llamar a otros métodos no seguros.SafeFoo
los métodos llaman aUnsafeFoo
métodos dentro de un candado.Es un poco detallado, pero comete errores obvios, como llamar a métodos inseguros fuera de un bloqueo en un método que se supone que es seguro para subprocesos, más evidente.
fuente
Aquí está mi opinión sobre la cola de tamaño fijo
Utiliza Cola normal, para evitar la sobrecarga de sincronización cuando
Count
se utiliza la propiedadConcurrentQueue
. También se implementaIReadOnlyCollection
para que se puedan utilizar los métodos LINQ. El resto es muy similar a las otras respuestas aquí.fuente
Solo por diversión, aquí hay otra implementación que creo que aborda la mayoría de las preocupaciones de los comentaristas. En particular, la seguridad de subprocesos se logra sin bloqueo y la implementación está oculta por la clase de envoltura.
fuente
_queue.Enqueue(obj)
pero antesInterlocked.Increment(ref _count)
, y el otro hilo llama.Count
? Obtendría un recuento incorrecto. No he comprobado los otros problemas.Mi versión es solo una subclase de las normales
Queue
... nada especial, pero al ver a todos participando y todavía va con el título del tema, mejor podría ponerlo aquí. También devuelve los quitados de la cola por si acaso.fuente
Agreguemos una respuesta más. ¿Por qué esto sobre otros?
1) Sencillez. Tratar de garantizar el tamaño es bueno, pero conduce a una complejidad innecesaria que puede presentar sus propios problemas.
2) Implementa IReadOnlyCollection, lo que significa que puede usar Linq en él y pasarlo a una variedad de cosas que esperan IEnumerable.
3) Sin bloqueo. Muchas de las soluciones anteriores usan candados, lo cual es incorrecto en una colección sin candados.
4) Implementa el mismo conjunto de métodos, propiedades e interfaces que hace ConcurrentQueue, incluido IProducerConsumerCollection, que es importante si desea utilizar la colección con BlockingCollection.
Esta implementación podría potencialmente terminar con más entradas de las esperadas si TryDequeue falla, pero la frecuencia con la que esto ocurre no parece ser un código especializado que inevitablemente obstaculizará el rendimiento y causará sus propios problemas inesperados.
Si absolutamente desea garantizar un tamaño, implementar un Prune () o un método similar parece ser la mejor idea. Puede usar un bloqueo de lectura ReaderWriterLockSlim en los otros métodos (incluido TryDequeue) y tomar un bloqueo de escritura solo al podar.
fuente
Solo porque nadie lo ha dicho todavía ... puedes usar a
LinkedList<T>
y agregar la seguridad de subprocesos:Una cosa a tener en cuenta es que el orden de enumeración predeterminado será LIFO en este ejemplo. Pero eso puede anularse si es necesario.
fuente
Para su placer de codificación, le presento el '
ConcurrentDeck
'Ejemplo de uso:
fuente
Bueno, depende del uso, he notado que algunas de las soluciones anteriores pueden exceder el tamaño cuando se usan en un entorno de subprocesos múltiples. De todos modos, mi caso de uso fue mostrar los últimos 5 eventos y hay varios subprocesos que escriben eventos en la cola y otro subproceso que lee y lo muestra en un control de Winform. Entonces esta fue mi solución.
EDITAR: Dado que ya estamos usando el bloqueo dentro de nuestra implementación, realmente no necesitamos ConcurrentQueue, puede mejorar el rendimiento.
EDITAR: Realmente no necesitamos
syncObject
en el ejemplo anterior y podemos usar elqueue
objeto ya que no estamos reinicializandoqueue
en ninguna función y está marcado como dereadonly
todos modos.fuente
La respuesta aceptada tendrá efectos secundarios evitables.
Los enlaces a continuación son referencias que utilicé cuando escribí mi ejemplo a continuación.
Si bien la documentación de Microsoft es un poco engañosa, ya que usan un bloqueo, sin embargo, bloquean las clases de segmento. Las propias clases de segmento utilizan Interlocked.
fuente
Aquí hay otra implementación que usa el ConcurrentQueue subyacente tanto como sea posible mientras proporciona las mismas interfaces disponibles a través de ConcurrentQueue.
fuente
Esta es mi versión de la cola:
Encuentro útil tener un constructor que se basa en un IEnumerable y me parece útil tener un GetSnapshot para tener una lista segura de múltiples subprocesos (matriz en este caso) de los elementos en el momento de la llamada, que no aumenta errores si cambia la colección subyacente.
La verificación de la cuenta doble es para evitar el bloqueo en algunas circunstancias.
fuente