新しいエンキュー時に古い値を自動的にデキューする固定サイズのキュー


120

私はConcurrentQueue共有データ構造に使用していますが、その目的は、それに渡された最後のN個のオブジェクト(一種の履歴)を保持することです。

ブラウザがあり、最新の100個のURLを参照したいとします。容量がいっぱいになると(履歴内の100アドレス)、新しいエントリの挿入(エンキュー)時に最も古い(最初の)エントリを自動的にドロップ(デキュー)するキューが必要です。

どうすればそれを達成できSystem.Collectionsますか?



それはあなたのために特別に意図されたのではなく、この質問に出くわし、それが役に立つと思うかもしれない人のためのものです。ところで、C#についても話します。(2分で)すべての回答を読み、C#コードがないことを理解できましたか?とにかく、私自身はよくわからないので、コメントです...

メソッドをロックでラップするだけです。高速であることを考えると、配列全体をロックするだけで済みます。これはたぶんだまされているかもしれません。C#コードで循環バッファー実装を検索すると、何かが見つかるかもしれません。とにかく、頑張ってください。

回答:


110

Enqueueでカウントをチェックし、カウントが制限を超えるとデキューするラッパークラスを記述します。

 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 2014

2
@KFL、ロックする必要があるためCountTryDequeue二つの独立した操作であるケアは、BCL同時で同期さないこと。
Richard Schneider

9
@RichardSchneider同時実行の問題を自分で処理する必要があるConcurrentQueue<T>場合は、Queue<T>オブジェクトをより軽量なオブジェクトに交換することをお勧めします。
0b101010 2014年

6
独自のキューを定義するのではなく、継承されたキューを使用してください。実行する場合、実際にはキューの値を使用して他に何もすることはできません。他のすべての関数Enqueueは、元のキューを呼び出します。つまり、この回答は承認済みとしてマークされていますが、完全に完全に壊れています。
ガーボル

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 2014

3
@mhandええ、私はあなたの言っていることがわかります。キューをラップし、キューの列挙子を公開して、Linq拡張機能を利用することができます。
Dave Lawrence

1
Enqueueメソッドは仮想ではないため、ConcurrentQueueを継承してはいけない@mhandに同意します。必要に応じて、キューをプロキシし、インターフェイス全体を実装する必要があります。
Chris Marisic

29

それが便利だと思う人のために、上記のリチャード・シュナイダーの答えに基づいたいくつかの実用的なコードがあります:

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の使用時にロックが悪い)のために投票を停止します。
ジョシュ

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ます。またIReadOnlyCollection、LINQメソッドを使用できるように実装されます。残りはここでの他の答えと非常に似ています。

[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 2017

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つ回答を追加しましょう。なぜこれが他の人よりも多いのですか?

1)シンプルさ。サイズを保証しようとすることは十分かつ適切ですが、独自の問題を示す可能性のある不要な複雑さにつながります。

2)IReadOnlyCollectionを実装します。つまり、Linqを使用して、IEnumerableを期待するさまざまなものに渡すことができます。

3)ロックなし。上記のソリューションの多くはロックを使用していますが、これはロックレスコレクションでは正しくありません。

4)IProducerConsumerCollectionを含む、ConcurrentQueueが行うのと同じメソッド、プロパティ、およびインターフェイスのセットを実装します。これは、BlockingCollectionでコレクションを使用する場合に重要です。

この実装は、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
私はこの実装が好きですが、何も追加されていない場合はdefault(T)を返すことに注意してください
Daniel Leach

この方法でロックを使用する場合は、ReaderWriterLockSlimを使用してリーダーに優先順位を付ける必要があります。
ジョシュ

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

まだ誰もそれを言っていないからといって..を使用し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のドキュメントはロックを使用しているため少し誤解を招きますが、セグメンテーションクラスはロックしています。セグメントクラス自体は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()は同じ機能を提供します。
ジョシュ

ConcurrentQueueはその実装でロックを処理します。私の回答のリンクを参照してください。
jjhayter
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.