새 enques시 이전 값을 자동으로 빼는 고정 크기 대기열


120

나는 ConcurrentQueue마지막 N 개의 객체 (일종의 역사)를 보유하는 공유 데이터 구조를 사용 하고 있습니다.

브라우저가 있고 마지막으로 검색된 URL 100 개를 갖고 싶다고 가정합니다. 용량이 가득 차면 새 항목 삽입 (대기열에 넣기)시 가장 오래된 (첫 번째) 항목을 자동으로 삭제 (대기열에서 빼기)하는 대기열을 원합니다 (기록에서 100 개 주소).

어떻게 사용 System.Collections합니까?



그것은 특별히 당신을위한 것이 아니라이 질문을 접하고 유용하다고 생각하는 사람을위한 것입니다. btw, C #에 대해서도 이야기합니다. 모든 답변 (2 분 안에) 을 읽고 거기에 C # 코드가 없다는 것을 알아 냈습니까? 어쨌든, 나는 나 자신을 확신하지 못하기 때문에 그것은 코멘트입니다 ...

메서드를 잠금으로 감쌀 수 있습니다. 속도가 빠르기 때문에 전체 어레이를 잠글 수 있습니다. 이것은 아마도 속임수 일 것입니다. C # 코드로 순환 버퍼 구현을 검색하면 무언가를 찾을 수 있습니다. 어쨌든 행운을 빕니다.

답변:


110

Enqueue에서 Count를 확인한 다음 개수가 제한을 초과하면 Dequeue를 수행하는 래퍼 클래스를 작성합니다.

 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)) ;
        }
     }
 }

4
q은 (는) lock다른 스레드가 동시에 액세스하는 것을 방지 하도록 개체에 대해 비공개 입니다.
Richard Schneider

14
잠그는 것은 좋은 생각이 아닙니다. BCL 동시 수집의 전체 목적은 성능상의 이유로 잠금없는 동시성을 제공하는 것입니다. 코드를 잠그면 그 이점이 손상됩니다. 실제로 deq를 잠 가야하는 이유를 알 수 없습니다.
KFL

2
@KFL이 필요 잠글 때문에 CountTryDequeueBCL 동시에 의해 서로 연관되지 않도록주의 두 개의 독립적 인 작업이다.
Richard Schneider

9
@RichardSchneider 동시성 문제를 처리해야하는 경우 자신은 그것을 바꿀 수있는 좋은 아이디어가 될 것이다 ConcurrentQueue<T>A의 객체를 Queue<T>더 경량 객체입니다.
0b101010 2014

6
자신의 대기열을 정의하지 말고 상속 된 대기열을 사용하십시오. 당신이하는 것처럼, 당신은 실제로 큐 값, 다른 모든 함수로 아무것도 할 수 없지만 새로운 함수 Enqueue는 여전히 원래 큐를 호출합니다. 즉,이 답변은 수락 된 것으로 표시되지만 완전히 완전히 깨졌습니다.
Gábor

104

나는 약간의 변형을 위해 갈 것입니다 ... FixedSizeQueue에서 Linq 확장을 사용할 수 있도록 ConcurrentQueue를 확장하십시오.

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);
            }
        }
    }
}

1
누군가가 인스턴스를 ConcurrentQueue <T>로 정적으로 알고 있으면 '새'키워드를 우회 한 것입니다.
mhand

6
@mhand 만약 '누군가'가 그렇게하고 싶다면; 그런 다음 그들은 ConcurrentQueue <T> 개체를 사용하여 시작하기로 선택했을 것입니다. 이것은 사용자 지정 저장소 클래스입니다. 아무도 이것을 .NET 프레임 워크에 제출하려고하지 않습니다. 당신은 그것을 위해 문제를 만들려고 노력했습니다.
Dave Lawrence

9
내 요점은 서브 클래 싱 대신 큐를 래핑해야 할 수도 있습니다 ... 이것은 모든 경우에 원하는 동작을 강제합니다. 또한 사용자 지정 스토리지 클래스이므로 완전히 사용자 지정하고 필요한 작업 만 노출하도록합시다. 여기서 서브 클래 싱은 잘못된 도구입니다.
mhand

3
@mhand 예, 무슨 말인지 알 수 있습니다. Linq 확장을 사용하기 위해 대기열을 래핑하고 대기열의 열거자를 노출 할 수 있습니다.
Dave Lawrence

1
Enqueue 메서드가 가상이 아니기 때문에 @mhand에 동의합니다. ConcurrentQueue를 상속해서는 안됩니다. 원하는 경우 대기열을 프록시하고 전체 인터페이스를 구현해야합니다.
Chris Marisic

29

유용하다고 생각하는 사람을 위해 위의 Richard Schneider의 답변을 기반으로 한 몇 가지 작업 코드가 있습니다.

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);
        }
    }
}

1
이것이 진정한 컬렉션이되기 위해 필요한 인터페이스를 구현하지 않는 것 외에도 언급 된 이유 (ConcurrentQueue 사용시 잠금이 좋지 않음)로 인해 투표를 거부합니다.
Josh

11

그 가치를 위해 여기에 안전하고 안전하지 않은 사용으로 표시된 몇 가지 메서드가 포함 된 경량 순환 버퍼가 있습니다.

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
}

나는 Foo()/SafeFoo()/UnsafeFoo()컨벤션 을 사용하고 싶습니다 .

  • Foo메소드 UnsafeFoo는 기본값으로 호출 됩니다.
  • UnsafeFoo 메서드는 잠금없이 자유롭게 상태를 수정하므로 다른 안전하지 않은 메서드 만 호출해야합니다.
  • SafeFoo메서드 UnsafeFoo는 잠금 내부의 메서드를 호출 합니다.

약간 장황하지만 스레드로부터 안전해야하는 메서드의 잠금 외부에서 안전하지 않은 메서드를 호출하는 것과 같은 명백한 오류가 발생합니다.


5

고정 크기 대기열에 대한 설명입니다.

Count속성이에서 사용될 때 동기화 오버 헤드를 피하기 위해 일반 큐를 사용합니다 ConcurrentQueue. 또한 IReadOnlyCollectionLINQ 메서드를 사용할 수 있도록 구현 합니다. 나머지는 여기의 다른 답변과 매우 유사합니다.

[Serializable]
[DebuggerDisplay("Count = {" + nameof(Count) + "}, Limit = {" + nameof(Limit) + "}")]
public class FixedSizedQueue<T> : IReadOnlyCollection<T>
{
    private readonly Queue<T> _queue = new Queue<T>();
    private readonly object _lock = new object();

    public int Count { get { lock (_lock) { return _queue.Count; } } }
    public int Limit { get; }

    public FixedSizedQueue(int limit)
    {
        if (limit < 1)
            throw new ArgumentOutOfRangeException(nameof(limit));

        Limit = limit;
    }

    public FixedSizedQueue(IEnumerable<T> collection)
    {
        if (collection is null || !collection.Any())
           throw new ArgumentException("Can not initialize the Queue with a null or empty collection", nameof(collection));

        _queue = new Queue<T>(collection);
        Limit = _queue.Count;
    }

    public void Enqueue(T obj)
    {
        lock (_lock)
        {
            _queue.Enqueue(obj);

            while (_queue.Count > Limit)
                _queue.Dequeue();
        }
    }

    public void Clear()
    {
        lock (_lock)
            _queue.Clear();
    }

    public IEnumerator<T> GetEnumerator()
    {
        lock (_lock)
            return new List<T>(_queue).GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

3

재미를 위해 여기에 댓글 작성자의 우려 사항을 대부분 해결한다고 생각하는 또 다른 구현이 있습니다. 특히 스레드 안전성은 잠금없이 달성되며 구현은 래핑 클래스에 의해 숨겨집니다.

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();
  }
}

1
이것은 동시에 사용하면 고장 - 쓰레드가 선점 어떤 경우 호출 한 후 _queue.Enqueue(obj)하지만 전에 Interlocked.Increment(ref _count), 다른 스레드 호출 .Count? 잘못된 카운트를 얻을 것입니다. 나는 다른 문제를 확인하지 않았습니다.
KFL

3

내 버전은 일반 버전의 하위 클래스 일뿐 Queue입니다. 특별한 것은 아니지만 모든 사람이 참여하는 모습을 볼 수 있으며 여기에 넣을 수있는 주제 제목과도 일치합니다. 또한 만일을 대비하여 대기열에서 제외 된 항목을 반환합니다.

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;
    }
}

2

답을 하나 더 추가하겠습니다. 왜 이것이 다른 사람보다?

1) 단순성. 크기를 보장하려는 시도는 훌륭하고 좋지만 자체 문제를 나타낼 수있는 불필요한 복잡성으로 이어집니다.

2) IReadOnlyCollection을 구현합니다. 즉, Linq를 사용하고 IEnumerable을 기대하는 다양한 항목에 전달할 수 있습니다.

3) 잠금 없음. 위의 많은 솔루션은 잠금이없는 컬렉션에서는 잘못된 잠금을 사용합니다.

4) BlockingCollection과 함께 컬렉션을 사용하려는 경우 중요한 IProducerConsumerCollection을 포함하여 ConcurrentQueue와 동일한 메서드, 속성 및 인터페이스 집합을 구현합니다.

이 구현은 TryDequeue가 실패 할 경우 예상보다 더 많은 항목으로 끝날 수 있지만 발생 빈도는 필연적으로 성능을 저하시키고 자체 예상치 못한 문제를 유발하는 특수 코드의 가치가 없어 보입니다.

절대적으로 크기를 보장하고 싶다면 Prune () 또는 유사한 메서드를 구현하는 것이 가장 좋은 생각 인 것 같습니다. 다른 메서드 (TryDequeue 포함)에서 ReaderWriterLockSlim 읽기 잠금을 사용하고 정리할 때만 쓰기 잠금을 사용할 수 있습니다.

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();
}

1

코딩의 즐거움을 위해 ' 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();
       }
   }
}

사용 예 :

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);
}

1
이 구현 같은 I 그러나 아무도가 등록되지 않은 경우는 기본 (T) 반환 참고 할
다니엘 리치

이러한 방식으로 잠금을 사용하는 경우 ReaderWriterLockSlim을 사용하여 독자의 우선 순위를 지정해야합니다.
Josh

1

위의 솔루션 중 일부가 다중 스레드 환경에서 사용될 때 크기를 초과 할 수 있음을 알아 차린 용도에 따라 다릅니다. 어쨌든 내 사용 사례는 마지막 5 개의 이벤트를 표시하는 것이었고 이벤트를 대기열에 쓰는 여러 스레드와 여기에서 읽고 Winform 컨트롤에 표시하는 다른 스레드가 있습니다. 그래서 이것이 제 해결책이었습니다.

편집 : 구현 내에서 이미 잠금을 사용하고 있으므로 ConcurrentQueue가 실제로 필요하지 않으므로 성능이 향상 될 수 있습니다.

class FixedSizedConcurrentQueue<T> 
{
    readonly Queue<T> queue = new Queue<T>();
    readonly object syncObject = new object();

    public int MaxSize { get; private set; }

    public FixedSizedConcurrentQueue(int maxSize)
    {
        MaxSize = maxSize;
    }

    public void Enqueue(T obj)
    {
        lock (syncObject)
        {
            queue.Enqueue(obj);
            while (queue.Count > MaxSize)
            {
                queue.Dequeue();
            }
        }
    }

    public T[] ToArray()
    {
        T[] result = null;
        lock (syncObject)
        {
            result = queue.ToArray();
        }

        return result;
    }

    public void Clear()
    {
        lock (syncObject)
        {
            queue.Clear();
        }
    }
}

편집 : syncObject위의 예 에서는 실제로 필요하지 않으며 어떤 함수에서도 queue다시 초기화하지 않고 어쨌든 queue표시 되기 때문에 객체를 사용할 수 있습니다 readonly.


1

아직 아무도 말하지 않았기 때문에 .. a를 사용 LinkedList<T>하고 스레드 안전성을 추가 할 수 있습니다 .

public class Buffer<T> : LinkedList<T>
{
    private int capacity;

    public Buffer(int capacity)
    {
        this.capacity = capacity;   
    }

    public void Enqueue(T item)
    {
        // todo: add synchronization mechanism
        if (Count == capacity) RemoveLast();
        AddFirst(item);
    }

    public T Dequeue()
    {
        // todo: add synchronization mechanism
        var last = Last.Value;
        RemoveLast();
        return last;
    }
}

한 가지주의 할 점은이 예제에서 기본 열거 순서가 LIFO라는 것입니다. 그러나 필요한 경우 재정의 할 수 있습니다.


0

허용되는 대답은 피할 수있는 부작용이있을 것입니다.

세밀한 잠금 및 잠금 해제 메커니즘

아래 링크는 아래에 예제를 작성할 때 사용한 참고 자료입니다.

Microsoft의 문서는 잠금을 사용하기 때문에 약간 오해의 소지가 있지만 segement 클래스를 잠급니다. 세그먼트 클래스 자체는 Interlocked를 사용합니다.

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;

namespace Lib.Core
{
    // Sources: 
    // https://docs.microsoft.com/en-us/dotnet/standard/collections/thread-safe/
    // https://docs.microsoft.com/en-us/dotnet/api/system.threading.interlocked?view=netcore-3.1
    // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueue.cs
    // https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Collections/Concurrent/ConcurrentQueueSegment.cs

    /// <summary>
    /// Concurrent safe circular buffer that will used a fixed capacity specified and resuse slots as it goes.
    /// </summary>
    /// <typeparam name="TObject">The object that you want to go into the slots.</typeparam>
    public class ConcurrentCircularBuffer<TObject>
    {
        private readonly ConcurrentQueue<TObject> _queue;

        public int Capacity { get; private set; }

        public ConcurrentCircularBuffer(int capacity)
        {
            if(capacity <= 0)
            {
                throw new ArgumentException($"The capacity specified '{capacity}' is not valid.", nameof(capacity));
            }

            // Setup the queue to the initial capacity using List's underlying implementation.
            _queue = new ConcurrentQueue<TObject>(new List<TObject>(capacity));

            Capacity = capacity;
        }

        public void Enqueue(TObject @object)
        {
            // Enforce the capacity first so the head can be used instead of the entire segment (slow).
            while (_queue.Count + 1 > Capacity)
            {
                if (!_queue.TryDequeue(out _))
                {
                    // Handle error condition however you want to ie throw, return validation object, etc.
                    var ex = new Exception("Concurrent Dequeue operation failed.");
                    ex.Data.Add("EnqueueObject", @object);
                    throw ex;
                }
            }

            // Place the item into the queue
            _queue.Enqueue(@object);
        }

        public TObject Dequeue()
        {
            if(_queue.TryDequeue(out var result))
            {
                return result;
            }

            return default;
        }
    }
}

0

ConcurrentQueue를 통해 사용 가능한 동일한 인터페이스를 제공하면서 가능한 한 기본 ConcurrentQueue를 사용하는 또 다른 구현이 있습니다.

/// <summary>
/// This is a FIFO concurrent queue that will remove the oldest added items when a given limit is reached.
/// </summary>
/// <typeparam name="TValue"></typeparam>
public class FixedSizedConcurrentQueue<TValue> : IProducerConsumerCollection<TValue>, IReadOnlyCollection<TValue>
{
    private readonly ConcurrentQueue<TValue> _queue;

    private readonly object _syncObject = new object();

    public int LimitSize { get; }

    public FixedSizedConcurrentQueue(int limit)
    {
        _queue = new ConcurrentQueue<TValue>();
        LimitSize = limit;
    }

    public FixedSizedConcurrentQueue(int limit, System.Collections.Generic.IEnumerable<TValue> collection)
    {
        _queue = new ConcurrentQueue<TValue>(collection);
        LimitSize = limit;

    }

    public int Count => _queue.Count;

    bool ICollection.IsSynchronized => ((ICollection) _queue).IsSynchronized;

    object ICollection.SyncRoot => ((ICollection)_queue).SyncRoot; 

    public bool IsEmpty => _queue.IsEmpty;

    // Not supported until .NET Standard 2.1
    //public void Clear() => _queue.Clear();

    public void CopyTo(TValue[] array, int index) => _queue.CopyTo(array, index);

    void ICollection.CopyTo(Array array, int index) => ((ICollection)_queue).CopyTo(array, index);

    public void Enqueue(TValue obj)
    {
        _queue.Enqueue(obj);
        lock( _syncObject )
        {
            while( _queue.Count > LimitSize ) {
                _queue.TryDequeue(out _);
            }
        }
    }

    public IEnumerator<TValue> GetEnumerator() => _queue.GetEnumerator();

    IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable<TValue>)this).GetEnumerator();

    public TValue[] ToArray() => _queue.ToArray();

    public bool TryAdd(TValue item)
    {
        Enqueue(item);
        return true;
    }

    bool IProducerConsumerCollection<TValue>.TryTake(out TValue item) => TryDequeue(out item);

    public bool TryDequeue(out TValue result) => _queue.TryDequeue(out result);

    public bool TryPeek(out TValue result) => _queue.TryPeek(out result);

}

-1

이것은 내 대기열 버전입니다.

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();
  }
}

IEnumerable을 기반으로 빌드 된 생성자를 갖는 것이 유용하다는 것을 알게되었고, 호출 시점에 항목의 다중 스레드 허용 목록 (이 경우 배열)을 갖도록 GetSnapshot을 사용하는 것이 유용하다는 것을 알게되었습니다. 밑받침 모음이 변경되면 오류가 발생합니다.

이중 카운트 검사는 일부 상황에서 잠금을 방지하는 것입니다.


1
대기열 잠금에 대한 투표. 절대적으로 잠 그려면 ReaderWriterLockSlim이 가장 좋습니다 (쓰기 잠금보다 읽기 잠금을 더 자주 사용할 것으로 예상한다고 가정). GetSnapshot도 필요하지 않습니다. IReadOnlyCollection <T> (IEnumerable 의미 체계에 필요함)을 구현하면 ToList ()가 동일한 함수를 제공합니다.
Josh

ConcurrentQueue는 구현에서 잠금을 처리합니다. 내 대답의 링크를 참조하십시오.
jjhayter
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.