RCIX RCIX - 3 years ago 82
C# Question

ObjectPool<T> or similar for .NET already in a library?

I don't want to write my own because i'm afraid i might miss something and/or rip off other people's work, so is there an ObjectPool (or similar) class existing in a library for .NET?

By object pool, i mean a class that assists caching of objects that take a long time to create, generally used to improve performance.

Answer Source

UPDATE:

I'd also put forward BufferBlock<T> from TPL DataFlow. IIRC it's part of .net now. The great thing about BufferBlock<T> is that you can wait asynchronously for items to become available using the Post<T> and ReceiveAsync<T> extension methods. Pretty handy in an async/await world.

ORIGINAL ANSWER

A while back I faced this problem and came up with a lightweight (rough'n'ready) threadsafe (I hope) pool that has proved very useful, reusable and robust:

    public class Pool<T> where T : class
    {
        private readonly Queue<AsyncResult<T>> asyncQueue = new Queue<AsyncResult<T>>();
        private readonly Func<T> createFunction;
        private readonly HashSet<T> pool;
        private readonly Action<T> resetFunction;

        public Pool(Func<T> createFunction, Action<T> resetFunction, int poolCapacity)
        {
            this.createFunction = createFunction;
            this.resetFunction = resetFunction;
            pool = new HashSet<T>();
            CreatePoolItems(poolCapacity);
        }

        public Pool(Func<T> createFunction, int poolCapacity) : this(createFunction, null, poolCapacity)
        {
        }

        public int Count
        {
            get
            {
                return pool.Count;
            }
        }

        private void CreatePoolItems(int numItems)
        {
            for (var i = 0; i < numItems; i++)
            {
                var item = createFunction();
                pool.Add(item);
            }
        }

        public void Push(T item)
        {
            if (item == null)
            {
                Console.WriteLine("Push-ing null item. ERROR");
                throw new ArgumentNullException();
            }
            if (resetFunction != null)
            {
                resetFunction(item);
            }
            lock (asyncQueue)
            {
                if (asyncQueue.Count > 0)
                {
                    var result = asyncQueue.Dequeue();
                    result.SetAsCompletedAsync(item);
                    return;
                }
            }
            lock (pool)
            {
                pool.Add(item);
            }
        }

        public T Pop()
        {
            T item;
            lock (pool)
            {
                if (pool.Count == 0)
                {
                    return null;
                }
                item = pool.First();
                pool.Remove(item);
            }
            return item;
        }

        public IAsyncResult BeginPop(AsyncCallback callback)
        {
            var result = new AsyncResult<T>();
            result.AsyncCallback = callback;
            lock (pool)
            {
                if (pool.Count == 0)
                {
                    lock (asyncQueue)
                    {
                        asyncQueue.Enqueue(result);
                        return result;
                    }
                }
                var poppedItem = pool.First();
                pool.Remove(poppedItem);
                result.SetAsCompleted(poppedItem);
                return result;
            }
        }

        public T EndPop(IAsyncResult asyncResult)
        {
            var result = (AsyncResult<T>) asyncResult;
            return result.EndInvoke();
        }
    }

In order to avoid any interface requirements of the pooled objects, both the creation and resetting of the objects is performed by user supplied delegates: i.e.

Pool<MemoryStream> msPool = new Pool<MemoryStream>(() => new MemoryStream(2048), pms => {
        pms.Position = 0;
        pms.SetLength(0);
    }, 500);

In the case that the pool is empty, the BeginPop/EndPop pair provide an APM (ish) means of retrieving the object asynchronously when one becomes available (using Jeff Richter's excellent AsyncResult<TResult> implementation).

I can't quite remember why it is constained to T : class... there's probably none.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download