visual valores utilizar usar tipos sintaxis los imprimir ejemplo como asignar arreglos arreglo array c# queue fifo

c# - utilizar - Cola de tamaño fijo que deque automáticamente los valores antiguos en nuevas enques



sintaxis array en c# (9)

Agreguemos una respuesta más. ¿Por qué esto sobre otros?

1) Simplicidad. Tratar de garantizar el tamaño es bueno pero lleva 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 bloqueos, lo cual es incorrecto en una colección sin bloqueos.

4) Implementa el mismo conjunto de métodos, propiedades e interfaces que ConcurrentQueue, incluido IProducerConsumerCollection, que es importante si desea usar la colección con BlockingCollection.

Esta implementación podría terminar con más entradas de lo esperado si TryDequeue falla, pero la frecuencia de lo que ocurre no parece valer la pena por un código especializado que inevitablemente obstaculizará el rendimiento y causará sus propios problemas inesperados.

Si 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.

class ConcurrentFixedSizeQueue<T> : IProducerConsumerCollection<T>, IReadOnlyCollection<T>, ICollection { readonly ConcurrentQueue<T> m_concurrentQueue; readonly int m_maxSize; public int Count => m_concurrentQueue.Count; public bool IsEmpty => m_concurrentQueue.IsEmpty; public ConcurrentFixedSizeQueue (int maxSize) : this(Array.Empty<T>(), maxSize) { } public ConcurrentFixedSizeQueue (IEnumerable<T> initialCollection, int maxSize) { if (initialCollection == null) { throw new ArgumentNullException(nameof(initialCollection)); } m_concurrentQueue = new ConcurrentQueue<T>(initialCollection); m_maxSize = maxSize; } public void Enqueue (T item) { m_concurrentQueue.Enqueue(item); if (m_concurrentQueue.Count > m_maxSize) { T result; m_concurrentQueue.TryDequeue(out result); } } public void TryPeek (out T result) => m_concurrentQueue.TryPeek(out result); public bool TryDequeue (out T result) => m_concurrentQueue.TryDequeue(out result); public void CopyTo (T[] array, int index) => m_concurrentQueue.CopyTo(array, index); public T[] ToArray () => m_concurrentQueue.ToArray(); public IEnumerator<T> GetEnumerator () => m_concurrentQueue.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator () => GetEnumerator(); // Explicit ICollection implementations. void ICollection.CopyTo (Array array, int index) => ((ICollection)m_concurrentQueue).CopyTo(array, index); object ICollection.SyncRoot => ((ICollection) m_concurrentQueue).SyncRoot; bool ICollection.IsSynchronized => ((ICollection) m_concurrentQueue).IsSynchronized; // Explicit IProducerConsumerCollection<T> implementations. bool IProducerConsumerCollection<T>.TryAdd (T item) => ((IProducerConsumerCollection<T>) m_concurrentQueue).TryAdd(item); bool IProducerConsumerCollection<T>.TryTake (out T item) => ((IProducerConsumerCollection<T>) m_concurrentQueue).TryTake(out item); public override int GetHashCode () => m_concurrentQueue.GetHashCode(); public override bool Equals (object obj) => m_concurrentQueue.Equals(obj); public override string ToString () => m_concurrentQueue.ToString(); }

Estoy usando ConcurrentQueue para una estructura de datos compartida cuyo propósito es retener los últimos N objetos que se le pasaron (tipo de historial).

Supongamos que tenemos un navegador y queremos tener las últimas 100 Urls examinadas. Quiero una cola que automáticamente deje (dequeue) la entrada más antigua (la primera) en la inserción de una nueva entrada (en cola) cuando la capacidad se llene (100 direcciones en el historial).

¿Cómo puedo lograr eso usando System.Collections ?


Escribía una clase contenedora que en Enqueue verificaba el recuento y luego Dequeue cuando el recuento excedía el límite.

public class FixedSizedQueue<T> { ConcurrentQueue<T> q = new ConcurrentQueue<T>(); private object lockObject = new object(); public int Limit { get; set; } public void Enqueue(T obj) { q.Enqueue(obj); lock (lockObject) { T overflow; while (q.Count > Limit && q.TryDequeue(out overflow)) ; } } }


Esta es mi versión de la cola:

public class FixedSizedQueue<T> { private object LOCK = new object(); ConcurrentQueue<T> queue; public int MaxSize { get; set; } public FixedSizedQueue(int maxSize, IEnumerable<T> items = null) { this.MaxSize = maxSize; if (items == null) { queue = new ConcurrentQueue<T>(); } else { queue = new ConcurrentQueue<T>(items); EnsureLimitConstraint(); } } public void Enqueue(T obj) { queue.Enqueue(obj); EnsureLimitConstraint(); } private void EnsureLimitConstraint() { if (queue.Count > MaxSize) { lock (LOCK) { T overflow; while (queue.Count > MaxSize) { queue.TryDequeue(out overflow); } } } } /// <summary> /// returns the current snapshot of the queue /// </summary> /// <returns></returns> public T[] GetSnapshot() { return queue.ToArray(); } }

Me parece útil tener un constructor basado en un IEnumerable y me parece útil tener un GetSnapshot para tener una lista de seguridad de múltiples subprocesos (array en este caso) de los elementos en el momento de la llamada, que no sube errores si la colección subyacente cambia

La verificación de doble conteo es para evitar el bloqueo en algunas circunstancias.


Me gustaría una pequeña variante ... extender ConcurrentQueue para poder usar extensiones Linq en FixedSizeQueue

public class FixedSizedQueue<T> : ConcurrentQueue<T> { private readonly object syncObject = new object(); public int Size { get; private set; } public FixedSizedQueue(int size) { Size = size; } public new void Enqueue(T obj) { base.Enqueue(obj); lock (syncObject) { while (base.Count > Size) { T outObj; base.TryDequeue(out outObj); } } } }


Mi versión es solo una subclase de los Queue normales. Nada especial, pero ver a todos participando y todavía va con el título del tema, también podría ponerlo aquí. También devuelve los descargados por si acaso.

public sealed class SizedQueue<T> : Queue<T> { public int FixedCapacity { get; } public SizedQueue(int fixedCapacity) { this.FixedCapacity = fixedCapacity; } /// <summary> /// If the total number of item exceed the capacity, the oldest ones automatically dequeues. /// </summary> /// <returns>The dequeued value, if any.</returns> public new T Enqueue(T item) { base.Enqueue(item); if (base.Count > FixedCapacity) { return base.Dequeue(); } return default; } }


Para cualquiera que lo encuentre útil, aquí hay un código de trabajo basado en la respuesta de Richard Schneider anterior:

public class FixedSizedQueue<T> { readonly ConcurrentQueue<T> queue = new ConcurrentQueue<T>(); public int Size { get; private set; } public FixedSizedQueue(int size) { Size = size; } public void Enqueue(T obj) { queue.Enqueue(obj); while (queue.Count > Size) { T outObj; queue.TryDequeue(out outObj); } } }


Para su placer de codificación, le presento el '' ConcurrentDeck ''

public class ConcurrentDeck<T> { private readonly int _size; private readonly T[] _buffer; private int _position = 0; public ConcurrentDeck(int size) { _size = size; _buffer = new T[size]; } public void Push(T item) { lock (this) { _buffer[_position] = item; _position++; if (_position == _size) _position = 0; } } public T[] ReadDeck() { lock (this) { return _buffer.Skip(_position).Union(_buffer.Take(_position)).ToArray(); } } }

Ejemplo de uso:

void Main() { var deck = new ConcurrentDeck<Tuple<string,DateTime>>(25); var handle = new ManualResetEventSlim(); var task1 = Task.Factory.StartNew(()=>{ var timer = new System.Timers.Timer(); timer.Elapsed += (s,a) => {deck.Push(new Tuple<string,DateTime>("task1",DateTime.Now));}; timer.Interval = System.TimeSpan.FromSeconds(1).TotalMilliseconds; timer.Enabled = true; handle.Wait(); }); var task2 = Task.Factory.StartNew(()=>{ var timer = new System.Timers.Timer(); timer.Elapsed += (s,a) => {deck.Push(new Tuple<string,DateTime>("task2",DateTime.Now));}; timer.Interval = System.TimeSpan.FromSeconds(.5).TotalMilliseconds; timer.Enabled = true; handle.Wait(); }); var task3 = Task.Factory.StartNew(()=>{ var timer = new System.Timers.Timer(); timer.Elapsed += (s,a) => {deck.Push(new Tuple<string,DateTime>("task3",DateTime.Now));}; timer.Interval = System.TimeSpan.FromSeconds(.25).TotalMilliseconds; timer.Enabled = true; handle.Wait(); }); System.Threading.Thread.Sleep(TimeSpan.FromSeconds(10)); handle.Set(); var outputtime = DateTime.Now; deck.ReadDeck().Select(d => new {Message = d.Item1, MilliDiff = (outputtime - d.Item2).TotalMilliseconds}).Dump(true); }


Por lo que vale, aquí hay un buffer circular ligero con algunos métodos marcados para un uso seguro e inseguro.

public class CircularBuffer<T> : IEnumerable<T> { readonly int size; readonly object locker; int count; int head; int rear; T[] values; public CircularBuffer(int max) { this.size = max; locker = new object(); count = 0; head = 0; rear = 0; values = new T[size]; } static int Incr(int index, int size) { return (index + 1) % size; } private void UnsafeEnsureQueueNotEmpty() { if (count == 0) throw new Exception("Empty queue"); } public int Size { get { return size; } } public object SyncRoot { get { return locker; } } #region Count public int Count { get { return UnsafeCount; } } public int SafeCount { get { lock (locker) { return UnsafeCount; } } } public int UnsafeCount { get { return count; } } #endregion #region Enqueue public void Enqueue(T obj) { UnsafeEnqueue(obj); } public void SafeEnqueue(T obj) { lock (locker) { UnsafeEnqueue(obj); } } public void UnsafeEnqueue(T obj) { values[rear] = obj; if (Count == Size) head = Incr(head, Size); rear = Incr(rear, Size); count = Math.Min(count + 1, Size); } #endregion #region Dequeue public T Dequeue() { return UnsafeDequeue(); } public T SafeDequeue() { lock (locker) { return UnsafeDequeue(); } } public T UnsafeDequeue() { UnsafeEnsureQueueNotEmpty(); T res = values[head]; values[head] = default(T); head = Incr(head, Size); count--; return res; } #endregion #region Peek public T Peek() { return UnsafePeek(); } public T SafePeek() { lock (locker) { return UnsafePeek(); } } public T UnsafePeek() { UnsafeEnsureQueueNotEmpty(); return values[head]; } #endregion #region GetEnumerator public IEnumerator<T> GetEnumerator() { return UnsafeGetEnumerator(); } public IEnumerator<T> SafeGetEnumerator() { lock (locker) { List<T> res = new List<T>(count); var enumerator = UnsafeGetEnumerator(); while (enumerator.MoveNext()) res.Add(enumerator.Current); return res.GetEnumerator(); } } public IEnumerator<T> UnsafeGetEnumerator() { int index = head; for (int i = 0; i < count; i++) { yield return values[index]; index = Incr(index, size); } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return this.GetEnumerator(); } #endregion }

Me gusta usar la convención Foo()/SafeFoo()/UnsafeFoo() :

  • Foo métodos de Foo llaman a UnsafeFoo por defecto.
  • UnsafeFoo métodos UnsafeFoo modifican el estado libremente sin un bloqueo, solo deben llamar a otros métodos inseguros.
  • SafeFoo métodos SafeFoo llaman a los métodos UnsafeFoo dentro de un bloqueo.

Es un poco detallado, pero produce 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 aparente.


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 hilos se logra sin bloqueo y la implementación está oculta por la clase de envoltura.

public class FixedSizeQueue<T> : IReadOnlyCollection<T> { private ConcurrentQueue<T> _queue = new ConcurrentQueue<T>(); private int _count; public int Limit { get; private set; } public FixedSizeQueue(int limit) { this.Limit = limit; } public void Enqueue(T obj) { _queue.Enqueue(obj); Interlocked.Increment(ref _count); // Calculate the number of items to be removed by this thread in a thread safe manner int currentCount; int finalCount; do { currentCount = _count; finalCount = Math.Min(currentCount, this.Limit); } while (currentCount != Interlocked.CompareExchange(ref _count, finalCount, currentCount)); T overflow; while (currentCount > finalCount && _queue.TryDequeue(out overflow)) currentCount--; } public int Count { get { return _count; } } public IEnumerator<T> GetEnumerator() { return _queue.GetEnumerator(); } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _queue.GetEnumerator(); } }