ASP. ObjectPool object reuse in. Net core

Posted by jaddy on Sat, 19 Feb 2022 11:42:53 +0100

preface

The last article mainly introduced the theoretical knowledge of ObjectPool, and then introduced Microsoft Extensions. How is ObjectPool implemented

Core components

ObjectPool

ObjectPool is a generic abstract interface. It abstracts two methods, Get and Return

  • The Get method is used to obtain available objects from the object pool. If the object is not available, create the object and return it
  • The Return method returns the object to the object pool
    /// <summary>
    /// A pool of objects.
    /// </summary>
    /// <typeparam name="T">The type of objects to pool.</typeparam>
    public abstract class ObjectPool<T> where T : class
    {
        /// <summary>
        /// Gets an object from the pool if one is available, otherwise creates one.
        /// </summary>
        /// <returns>A <typeparamref name="T"/>.</returns>
        public abstract T Get();

        /// <summary>
        /// Return an object to the pool.
        /// </summary>
        /// <param name="obj">The object to add to the pool.</param>
        public abstract void Return(T obj);
    }


ObjectPoolProvider

ObjectPoolProvider is an abstract interface. It has built-in generic method of Create and generic abstract method of Create. It is based on default policy.

    /// <summary>
    /// A provider of <see cref="ObjectPool{T}"/> instances.
    /// </summary>
    public abstract class ObjectPoolProvider
    {
        /// <summary>
        /// Creates an <see cref="ObjectPool"/>.
        /// </summary>
        /// <typeparam name="T">The type to create a pool for.</typeparam>
        public ObjectPool<T> Create<T>() where T : class, new()
        {
            return Create<T>(new DefaultPooledObjectPolicy<T>());
        }

        /// <summary>
        /// Creates an <see cref="ObjectPool"/> with the given <see cref="IPooledObjectPolicy{T}"/>.
        /// </summary>
        /// <typeparam name="T">The type to create a pool for.</typeparam>
        public abstract ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) where T : class;
    }
    

IPooledObjectPolicy

IPooledObjectPolicy is a generic interface that provides a pool of policy management objects. This class also defines two methods, Create and Return, to provide policy implementation

  • Create is used to create related class instances
  • Return is used to put the used object back into the pool, including resetting the object state and whether it can be put back into the pool
    /// <summary>
    /// Represents a policy for managing pooled objects.
    /// </summary>
    /// <typeparam name="T">The type of object which is being pooled.</typeparam>
    public interface IPooledObjectPolicy<T>
    {
        /// <summary>
        /// Create a <typeparamref name="T"/>.
        /// </summary>
        /// <returns>The <typeparamref name="T"/> which was created.</returns>
        T Create();

        /// <summary>
        /// Runs some processing when an object was returned to the pool. Can be used to reset the state of an object and indicate if the object should be returned to the pool.
        /// </summary>
        /// <param name="obj">The object to return to the pool.</param>
        /// <returns><code>true</code> if the object should be returned to the pool. <code>false</code> if it's not possible/desirable for the pool to keep the object.</returns>
        bool Return(T obj);
    }

PooledObjectPolicy is a generic abstract class, which implements IPooledObjectPolicy and provides two abstract methods

    public abstract class PooledObjectPolicy<T> : IPooledObjectPolicy<T>
    {
        public abstract T Create();

        public abstract bool Return(T obj);
    }

Realization mechanism

DefaultObjectPool

DefaultObjectPool implements ObjectPool, interlocked Compareexchange (Ref _firstItem, null, item) will_ Compare the value of firstItem with the value of item. If it is equal, replace it with null_ firstItem, otherwise no operation will be performed. No matter whether it is replaced or not, the returned items will be saved in the original_ The value of firstItem.

Interlocked can provide atomic operations on variables shared by multiple threads.

  • Interlocked.Increment: increment the value of the specified variable in the form of atomic operation and store the result.
  • Interlocked.Decrement decrements the value of a specified variable in the form of an atomic operation and stores the result.
  • Interlocked.Add adds two integers in the form of atomic operations and replaces the first integer with the sum of both
        public override T Get()
        {
            var item = _firstItem;
            if (item == null || Interlocked.CompareExchange(ref _firstItem, null, item) != item)
            {
                var items = _items;
                for (var i = 0; i < items.Length; i++)
                {
                    item = items[i].Element;
                    if (item != null && Interlocked.CompareExchange(ref items[i].Element, null, item) == item)
                    {
                        return item;
                    }
                }

                item = Create();
            }

            return item;
        }

        public override void Return(T obj)
        {
            if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj)))
            {
                if (_firstItem != null || Interlocked.CompareExchange(ref _firstItem, obj, null) != null)
                {
                    var items = _items;
                    for (var i = 0; i < items.Length && Interlocked.CompareExchange(ref items[i].Element, obj, null) != null; ++i)
                    {
                    }
                }
            }
        }

DefaultObjectPoolProvider

DefaultObjectPoolProvider overrides the Crearte method in ObjectPoolProvider,
The default maximum number of objects is set. Only the default environment can be used Twice the CPU of processorcount

    /// <summary>
    /// The default <see cref="ObjectPoolProvider"/>.
    /// </summary>
    public class DefaultObjectPoolProvider : ObjectPoolProvider
    {
        /// <summary>
        /// The maximum number of objects to retain in the pool.
        /// </summary>
        public int MaximumRetained { get; set; } = Environment.ProcessorCount * 2;

        /// <inheritdoc/>
        public override ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy)
        {
            if (policy == null)
            {
                throw new ArgumentNullException(nameof(policy));
            }

            if (typeof(IDisposable).IsAssignableFrom(typeof(T)))
            {
                return new DisposableObjectPool<T>(policy, MaximumRetained);
            }

            return new DefaultObjectPool<T>(policy, MaximumRetained);
        }
    }


DisposableObjectPool

DisposableObjectPool inherits the DefaultObjectPool and implements IDisposable to recycle objects manually

      public void Dispose()
        {
            _isDisposed = true;

            DisposeItem(_firstItem);
            _firstItem = null;

            ObjectWrapper[] items = _items;
            for (var i = 0; i < items.Length; i++)
            {
                DisposeItem(items[i].Element);
                items[i].Element = null;
            }
        }

        private void DisposeItem(T item)
        {
            if (item is IDisposable disposable)
            {
                disposable.Dispose();
            }
        }


LeakTrackingObjectPool

LeakTrackingObjectPool implements ObjectPool, which defines ConditionalWeakTable. It is a weak reference dictionary. All keys and values in ConditionalWeakTable < tkey, tvalue > are weakly referenced, and will automatically disappear from the collection after their keys are recycled or both keys and values are recycled. This means that when you use it to attach some fields or attributes to a type, you don't have to worry about memory leakage at all

    public class LeakTrackingObjectPool<T> : ObjectPool<T> where T : class
    {
        private readonly ConditionalWeakTable<T, Tracker> _trackers = new ConditionalWeakTable<T, Tracker>();
        private readonly ObjectPool<T> _inner;

        public LeakTrackingObjectPool(ObjectPool<T> inner)
        {
            if (inner == null)
            {
                throw new ArgumentNullException(nameof(inner));
            }

            _inner = inner;
        }

        public override T Get()
        {
            var value = _inner.Get();
            _trackers.Add(value, new Tracker());
            return value;
        }

        public override void Return(T obj)
        {
            Tracker tracker;
            if (_trackers.TryGetValue(obj, out tracker))
            {
                _trackers.Remove(obj);
                tracker.Dispose();
            }

            _inner.Return(obj);
        }

        private class Tracker : IDisposable
        {
            private readonly string _stack;
            private bool _disposed;

            public Tracker()
            {
                _stack = Environment.StackTrace;
            }

            public void Dispose()
            {
                _disposed = true;
                GC.SuppressFinalize(this);
            }

            ~Tracker()
            {
                if (!_disposed && !Environment.HasShutdownStarted)
                {
                    Debug.Fail($"{typeof(T).Name} was leaked. Created at: {Environment.NewLine}{_stack}");
                }
            }
        }
    }

reference resources

https://blog.walterlv.com/post/conditional-weak-table.html

https://www.cnblogs.com/edison0621/p/11747912.html