設計一個物件池(Anno.XObjectPool)

杜燕明發表於2021-05-13

設計一個.net物件池  

     物件池對於建立開銷比較大的物件來說很有意義,為了優化程式的執行速度、避免頻繁建立銷燬開銷比較大的物件,我們可以通過物件池來複用建立開銷大的物件。物件池的思路比較簡單,事先建立好一批物件,放到一個集合中,以後每當程式需要新的物件時候,都從物件池裡獲取,每當程式用完該物件後,都把該物件歸還給物件池。這樣會避免重複的物件建立,提高程式效能。 

應用場景 

  在Anno微服務框架中的使用,由於客戶端呼叫微服的時候需要建立Socket連線,頻繁的建立和銷燬連線會有很大的開銷。所以我們設想我們如果可以重複利用這些物件那麼效能上也會是很大的提升。這時候我們就需要一個物件池來存放這些可以重複利用的物件。不僅如此我們還需要可以控制物件池的最大存活物件數量、最小閒置物件數量、最大閒置物件數量、如何建立物件、銷燬物件、定期清理閒置物件。(網上沒找到好用的於是開始造我們的輪子)

Install-Package Anno.XObjectPool -Version 1.0.3.4

Xpool核心程式碼

設計一個物件池(Anno.XObjectPool)
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Anno.XObjectPool
{
    public class XPool<T> : IDisposable
    {
        private bool disposed;
        private XPoolConfiguration xcfg;
        /// <summary>
        /// 初始化物件池
        /// </summary>
        /// <param name="createObject">建立XObject物件</param>
        /// <param name="activeXObject"> 獲取XObject物件之前驗證True 有效</param>
        public XPool(Func<T> createObject, Func<T, bool> activeXObject = null) : this(new XPoolConfiguration()
        {
            MaxActive = 1000,
            MaxIdle = 400,
            MinIdle = 10
        }, createObject, activeXObject)
        {

        }
        /// <summary>
        /// 初始化物件池
        /// </summary>
        /// <param name="maxActive">最大活動數量</param>
        /// <param name="minIdle">最小空閒數量</param>
        /// <param name="maxIdle">最大空閒數量</param>
        /// <param name="createObject">建立XObject物件</param>
        /// <param name="activeXObject"> 獲取XObject物件之前驗證True 有效</param>
        public XPool(int maxActive, int minIdle, int maxIdle, Func<T> createObject, Func<T, bool> activeXObject = null)
        {
            xcfg = new XPoolConfiguration()
            {
                MaxActive = maxActive,
                MaxIdle = maxIdle,
                MinIdle = minIdle
            };
            pools = new ConcurrentStack<XObject<T>>();
            ResetEvent = new AutoResetEvent(false);
            if (createObject != null)
            {
                CreateXObject = createObject;
            }
            else
            {
                throw new ArgumentNullException("createObject 不能為空");
            }
            if (activeXObject != null)
            {
                ActiveXObject = activeXObject;
            }
            Parallel.For(0, minIdle, x =>
            {
                pools.Push(new XObject<T>()
                {
                    Value = CreateXObject.Invoke(),
                    LastDateTime = DateTime.Now,
                    Pool = this
                });
            });
            StartTaskClearLongIdleXObject();
        }
        /// <summary>
        /// 初始化物件池
        /// </summary>
        /// <param name="xcfg">物件池配置</param>
        /// <param name="createObject">建立XObject物件</param>
        /// <param name="activeXObject"> 獲取XObject物件之前驗證True 有效</param>
        public XPool(XPoolConfiguration xcfg, Func<T> createObject, Func<T, bool> activeXObject = null) : this(xcfg.MaxActive, xcfg.MinIdle, xcfg.MaxIdle, createObject, activeXObject)
        {

        }
        private ConcurrentStack<XObject<T>> pools;
        private int _activedTransportCount = 0;
        private AutoResetEvent ResetEvent { get; set; }
        /// <summary>
        /// 活動連結數量
        /// </summary>
        public int ActivedTransportCount => _activedTransportCount;

        /// <summary>
        /// 原子性增加 活動連結數量
        /// </summary>
        private void InterlockedIncrement()
        {
            Interlocked.Increment(ref _activedTransportCount);
        }
        /// <summary>
        /// 原子性減少 活動連結數量
        /// </summary>
        private void InterlockedDecrement()
        {
            Interlocked.Decrement(ref _activedTransportCount);
        }

        public XObject<T> Borrow(TimeSpan? timeout = null)
        {
            if (!pools.TryPop(out XObject<T> xobj))
            {
                if (pools.Count < xcfg.MinIdle && _activedTransportCount < xcfg.MaxActive)
                {
                    pools.Push(new XObject<T>()
                    {
                        Value = CreateXObject.Invoke(),
                        LastDateTime = DateTime.Now,
                        Pool = this
                    });
                }
                if (!pools.Any() && _activedTransportCount >= xcfg.MaxActive)
                {
                    int millisecondsTimeout = 20000;
                    if (timeout.HasValue && timeout.Value.TotalMilliseconds > 0)
                    {
                        millisecondsTimeout = (int)timeout.Value.TotalMilliseconds;
                    }
                    bool result = ResetEvent.WaitOne(millisecondsTimeout);
                    if (!result)
                    {
                        throw new TimeoutException($"Timeout物件池等待超時!");
                    }
                }
                if (!pools.TryPop(out xobj))
                {
                    xobj = new XObject<T>()
                    {
                        Value = CreateXObject.Invoke(),
                        LastDateTime = DateTime.Now,
                        Pool = this
                    };
                }
            }
            InterlockedIncrement();
            //借出之前判斷物件是否有效
            if (!ActiveXObject(xobj.Value))
            {
                throw new InvalidOperationException("物件無效,請在有效性檢測函式activeXObject中設定有效性");
            }
            return xobj;
        }
        public void Return(XObject<T> xObject, bool isDispose = false)
        {
            if (xObject == null)
            {
                throw new ArgumentNullException("xObject 不能為空!");
            }
            /*
             * 主動釋放的釋放
             * 超出最大閒置數量的釋放
             * 無效的釋放
             */
            if (isDispose || _activedTransportCount > xcfg.MaxIdle || !ActiveXObject(xObject.Value))
            {
                DisposeXObject(xObject);
                xObject.Pool = null;
                InterlockedDecrement();
                return;
            }
            xObject.LastDateTime = DateTime.Now;
            pools.Push(xObject);
            InterlockedDecrement();
            ResetEvent.Set();
        }


        private void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    try
                    {
                        while (pools.TryPop(out XObject<T> xobj))
                        {
                            //Pool 釋放的時候XObject不再歸還到Pool
                            DisposeXObject(xobj);
                            xobj.Pool = null;
                        }
                    }
                    catch (Exception)
                    {

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

        /// <summary>
        /// 建立XObject物件
        /// </summary>
        public Func<T> CreateXObject { get; set; } = () => { return default(T); };
        /// <summary>
        /// 獲取XObject物件之前驗證True 有效
        /// </summary>
        public Func<T, bool> ActiveXObject { get; set; } = x => { return true; };
        /// <summary>
        /// 釋放XObject時候觸發
        /// </summary>
        public Action<XObject<T>> DisposeXObject { get; set; } = x => { };
        /// <summary>
        /// 移除長度為count的元素
        /// </summary>
        /// <param name="count">除元素的長度count</param>
        private void DisposeLongIdleXObject(int count)
        {
            int startIndex = pools.Count - count;
            XObject<T>[] popXObjects = new XObject<T>[count];
            pools.TryPopRange(popXObjects, 0, count);
            for (int i = 0; i < popXObjects.Length; i++)
            {
                Return(popXObjects[i], true);
            }
        }
        /// <summary>
        /// 每隔10秒檢測一次清理30秒未使用的物件數量的物件
        /// (如果存在物件30未使用,說明物件池有物件長時間閒置未使用)則從頭部彈出一定數量的物件釋放掉
        /// </summary>
        private void StartTaskClearLongIdleXObject()
        {
            Task.Factory.StartNew(async () =>
            {
                while (!disposed)
                {
                    await Task.Delay(10000);
                    try
                    {
                        var removeCount = 0;
                        var now = DateTime.Now.AddSeconds(-30);
                        var _pools = pools.ToList();
                        for (int i = _pools.Count - 1; i >= xcfg.MinIdle; i--)
                        {
                            if (_pools[i].LastDateTime < now)
                            {
                                removeCount++;
                            }
                        }
                        if (removeCount > 0 && removeCount <= (pools.Count - xcfg.MinIdle))
                        {
                            DisposeLongIdleXObject(removeCount);
                        }
                    }
                    finally { }
                }
            }, TaskCreationOptions.LongRunning);
        }
    }
}
View Code

初始化一個物件池

  最大活動物件數量 50個,最小閒置物件數量2個,最大閒置數量20個。

var UserPool = new XPool<User>(50, 2, 20, () =>
             {
                 int age = Interlocked.Increment(ref _activedTransportCount);
                 return new User()
                 {
                     Age = age,
                     Name = $"Name{age}"

                 };
             });

並行呼叫

  200個並行呼叫

  Parallel.For(0, 200, x =>
  {
                using (var user = UserPool.Borrow())
                {
                    Console.WriteLine($"Age:{user.Value.Age},Name:{user.Value.Name}");//,Msg:{user.Value.Msg}
                }
   });

 結果:

  從上圖我們看到在200個並行過程中,只有4個物件被使用。因此可以看出我們沒有頻繁的建立物件。

 

歡迎加入QQ群:478399354 ,到這裡我們互為師長專案學習。

Anno開源地址:

AnnoGitHub原始碼:https://github.com/duyanming/Anno.Core  

AnnoGitee原始碼:https://gitee.com/dotnetchina/anno.core 

Viper示例專案:https://github.com/duyanming/Viper  

體驗地址:http://140.143.207.244/Home/Login

文件地址:https://duyanming.github.io/

關於Anno的更多內容,隨後更新。敬請關注。開源不易,感謝Star。

相關文章