《編程篇》已經涉及到了物件池模型的大部分核心介面和型別,物件池模型其實是很簡單的,不過其中有一些為了提升性能而刻意為之的實作細節倒是值得我們關注,總的來說,物件池模型由三個核心物件構成,它們分別是表示物件池的ObjectPool<T>物件、物件值提供者的ObjectPoolProvider物件,已及控制池化物件創建與釋放行為的IPooledObjectPolicy<T>物件,我們先來介紹最后一個物件,
目錄
一、 IPooledObjectPolicy<T>
二、ObjectPool<T>
DefaultObjectPool<T>
DisposableObjectPool<T>
三、ObjectPoolProvider
一、 IPooledObjectPolicy<T>
我們在《編程篇》已經說過,表示池化物件策略的IPooledObjectPolicy<T>物件不僅僅幫助我們創建物件,還可以幫助我們執行一些物件回歸物件池之前所需的回收操作,物件最終能否回到物件池中也受它的控制,如下面的代碼片段所示,IPooledObjectPolicy<T>介面定義了兩個方法,Create方法用來創建池化物件,物件回歸前需要執行的操作體現在Return方法上,該方法的回傳值決定了指定的物件是否應該回歸物件池,抽象類PooledObjectPolicy<T>實作了該介面,我們一般將它作為自定義策略型別的基類,
public interface IPooledObjectPolicy<T> { T Create(); bool Return(T obj); } public abstract class PooledObjectPolicy<T> : IPooledObjectPolicy<T> { protected PooledObjectPolicy(){} public abstract T Create(); public abstract bool Return(T obj); }
我們默認使用的是如下這個DefaultPooledObjectPolicy<T>型別,由于它直接通過反射來創建池化物件,所以要求泛型引數T必須有一個公共的默認無參建構式,它的Return方法直接回傳True,意味著提供的物件可以被無限制地復用,
public class DefaultPooledObjectPolicy<T> : PooledObjectPolicy<T> where T: class, new() { public override T Create() => Activator.CreateInstance<T>(); public override bool Return(T obj) => true; }
二、ObjectPool<T>
物件池通過ObjectPool<T>物件表示,如下面的代碼片段所示,ObjectPool<T>是一個抽象類,池化物件通過Get方法提供給我們,我們在使用完之后呼叫Return方法將其釋放到物件池中以供后續復用,
public abstract class ObjectPool<T> where T: class { protected ObjectPool(){} public abstract T Get(); public abstract void Return(T obj); }
DefaultObjectPool<T>
我們默認使用的物件池體現為一個DefaultObjectPool<T>物件,由于針對物件池的絕大部分實作就體現這個型別中,所以它也是本節重點講述的內容,我們在前面一節已經說過,物件池具有固定的大小,并且默認的大小為處理器個數的2倍,我們假設物件池的大小為N,那么DefaultObjectPool<T>物件會如下圖所示的方式使用一個單一對象和一個長度為N-1的陣列來存放由它提供的N個物件,
![]()
如下面的代碼片段所示,DefaultObjectPool<T>使用欄位_firstItem用來存放第一個池化物件,余下的則存放在_items欄位表示的陣列中,值得注意的是,這個陣列的元素型別并非池化物件的型別T,而是一個封裝了池化物件的結構體ObjectWrapper,如果該陣列元素型別改為參考型別T,那么當我們對某個元素進行復制的時候,運行時會進行型別校驗(要求指定物件型別派生于T),無形之中帶來了一定的性能損失(值型別陣列就不需求進行派生型別的校驗),我們在前面提到過,物件池中存在一些性能優化的細節,這就是其中之一,
public class DefaultObjectPool<T> : ObjectPool<T> where T : class { private protected T _firstItem; private protected readonly ObjectWrapper[] _items; … private protected struct ObjectWrapper { public T Element; } }
DefaultObjectPool<T>型別定義了如下兩個建構式,我們在創建一個DefaultObjectPool<T>物件的時候會提供一個IPooledObjectPolicy<T>物件并指定物件池的大小,物件池的大小默認設定為處理器數量的2倍體現在第一個建構式多載中,如果指定的是一個DefaultPooledObjectPolicy<T>物件,表示默認池化物件策略的_isDefaultPolicy欄位被設定成True,因為DefaultPooledObjectPolicy<T>物件的Return方法總是回傳True,并且沒有任何具體的操作,所以在將物件釋放回物件池的時候就不需要呼叫Return方法了,這是第二個性能優化的細節,
public class DefaultObjectPool<T> : ObjectPool<T> where T : class { private protected T _firstItem; private protected readonly ObjectWrapper[] _items; private protected readonly IPooledObjectPolicy<T> _policy; private protected readonly bool _isDefaultPolicy; private protected readonly PooledObjectPolicy<T> _fastPolicy; public DefaultObjectPool(IPooledObjectPolicy<T> policy) : this(policy, Environment.ProcessorCount * 2) {} public DefaultObjectPool(IPooledObjectPolicy<T> policy, int maximumRetained) { _policy = policy ; _fastPolicy = policy as PooledObjectPolicy<T>; _isDefaultPolicy = IsDefaultPolicy(); _items = new ObjectWrapper[maximumRetained - 1]; bool IsDefaultPolicy() { var type = policy.GetType(); return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(DefaultPooledObjectPolicy<>); } } [MethodImpl(MethodImplOptions.NoInlining)] private T Create() => _fastPolicy?.Create() ?? _policy.Create(); }
從第二個建構式的定義可以看出,指定的IPooledObjectPolicy<T>物件除了會賦值給_policy欄位之外,如果提供的是一個PooledObjectPolicy<T>物件,該物件還會同時賦值給另一個名為_fastPolicy的欄位,在進行池化物件的提取和釋放時,_fastPolicy欄位表示的池化物件策略會優先選用,這個邏輯體現在Create方法上,因為呼叫型別的方法比呼叫介面方法具有更好的性能(所以該欄位才會命名為_fastPolicy),這是第三個性能優化的細節,這個細節還告訴我們在自定義池化物件策略的時候,最好將PooledObjectPolicy<T>作為基類,而不是直接實作IPooledObjectPolicy<T>介面,
如下所示的是重寫的Get和Return方法的定義,用于提供池化物件的Get方法很簡單,它會采用原子操作使用Null將_firstItem欄位表示的物件“替換”下來,如果該欄位不為Null,那么將其作為回傳的物件,反之它會遍歷陣列的每個ObjectWrapper物件,并使用Null將其封裝的物件“替換”下來,第一個成功替換下來的物件將作為回傳值,如果所有ObjectWrapper物件封裝的物件都為Null,意味著所有物件都被“借出”或者尚未創建,此時回傳創建的新物件了,
public class DefaultObjectPool<T> : ObjectPool<T> where T : class { 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) {} } } } … }
將物件釋放會物件池的Return方法也很好理解,首先它需要判斷指定的物件能否釋放會物件池中,如果使用的是默認的池化物件策略,答案是肯定的,否則只能通過呼叫IPooledObjectPolicy<T>物件的Return方法來判斷,從代碼片段可以看出,這里依然會優先選擇_fastPolicy欄位表示的PooledObjectPolicy<T>物件以獲得更好的性能,
在確定指定的物件可以釋放回物件之后,如果_firstItem欄位為Null,Return方法會采用原子操作使用指定的物件將其“替換”下來,如果該欄位不為Null或者原子替換失敗,該方法會便利陣列的每個ObjectWrapper物件,并采用原子操作將它們封裝的空參考替換成指定的物件,整個方法會在某個原子替換操作成功或者整個便利程序結束之后回傳,
DefaultObjectPool<T>之所有使用一個陣列附加一個單一物件來存盤池化物件,是因為針對單一欄位的讀寫比針對陣列元素的讀寫具有更好的性能,從上面給出的代碼可以看出,不論是Get還是Return方法,優先選擇的都是_firstItem欄位,如果池化物件的使用率不高,基本上使用的都會是該欄位存盤的物件,那么此時的性能是最高的,
DisposableObjectPool<T>
通過前面的示例演示我們知道,當池化物件型別實作了IDisposable介面的情況下,如果某個物件在回歸物件池的時候,物件池已滿,該物件將被丟棄,與此同時,被丟棄物件的Dispose方法將立即被呼叫,但是這種現象并沒有在DefaultObjectPool<T>型別的代碼中體現出來,這是為什么呢?實際上DefaultObjectPool<T>還有如下這個名為DisposableObjectPool<T>的派生類,如代碼片段可以看出,表示池化物件型別的泛型引數T要求實作IDisposable介面,如果池化物件型別實作了IDisposable介面,通過默認ObjectPoolProvider物件創建的物件池就是一個DisposableObjectPool<T>物件,
internal sealed class DisposableObjectPool<T> : DefaultObjectPool<T>, IDisposable where T : class { private volatile bool _isDisposed; public DisposableObjectPool(IPooledObjectPolicy<T> policy) : base(policy) {} public DisposableObjectPool(IPooledObjectPolicy<T> policy, int maximumRetained) : base(policy, maximumRetained) {} public override T Get() { if (_isDisposed) { throw new ObjectDisposedException(GetType().Name); } return base.Get(); } public override void Return(T obj) { if (_isDisposed || !ReturnCore(obj)) { DisposeItem(obj); } } private bool ReturnCore(T obj) { bool returnedToPool = false; if (_isDefaultPolicy || (_fastPolicy?.Return(obj) ?? _policy.Return(obj))) { if (_firstItem == null && Interlocked.CompareExchange(ref _firstItem, obj, null) == null) { returnedToPool = true; } else { var items = _items; for (var i = 0; i < items.Length && !(returnedTooPool = Interlocked.CompareExchange(ref items[i].Element, obj, null) == null); i++) {} } } return returnedTooPool; } 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(); } } }
從上面代碼片段可以看出,DisposableObjectPool<T>自身型別也實作了IDisposable介面,它會在Dispose方法中呼叫目前物件池中的每個物件的Dispose方法,用于提供池化物件的Get方法除了會驗證自身的Disposed狀態之外,并沒有特別之處,當物件未能成功回歸物件池,通過呼叫該物件的Dispose方法將其釋放的操作體現在重寫的Return方法中,
三、ObjectPoolProvider
表示物件池的ObjectPool<T>物件是通過ObjectPoolProvider提供的,如下面的代碼片段所示,抽象類ObjectPoolProvider定義了兩個多載的Create<T>方法,抽象方法需要指定具體的池化物件策略,另一個多載由于采用默認的池化物件策略,所以要求物件型別具有一個默認無參建構式,
public abstract class ObjectPoolProvider { public ObjectPool<T> Create<T>() where T : class, new() => Create<T>(new DefaultPooledObjectPolicy<T>()); public abstract ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) where T : class; }
在前面的示例演示中,我們使用的是如下這個DefaultObjectPoolProvider型別,如代碼片段所示,DefaultObjectPoolProvider派生于抽象類ObjectPoolProvider,在重寫的Create<T>方法中,它會根據泛型引數T是否實作IDisposable介面分別創建DisposableObjectPool<T>和DefaultObjectPool<T>物件,
public class DefaultObjectPoolProvider : ObjectPoolProvider { public int MaximumRetained { get; set; } = Environment.ProcessorCount * 2; public override ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) => typeof(IDisposable).IsAssignableFrom(typeof(T)) ? new DisposableObjectPool<T>(policy, MaximumRetained) : new DefaultObjectPool<T>(policy, MaximumRetained); }
DefaultObjectPoolProvider型別定義了一個標識物件池大小的MaximumRetained屬性,采用處理器數量的兩倍作為默認容量也體現在這里,這個屬性并非只讀,所以我們可以利用它根據具體需求調整提供物件池的大小,在ASP.NET應用中,我們基本上都會采用依賴注入的方式利用注入的ObjectPoolProvider物件來創建針對具體型別的物件池,我們在《編程篇》還演示了另一種創建物件池的方式,那就是直接呼叫ObjectPool型別的靜態Create<T>方法,該方法的實作體現在如下所示的代碼片段中,
public static class ObjectPool { public static ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) where T: class, new() => new DefaultObjectPoolProvider().Create<T>(policy ?? new DefaultPooledObjectPolicy<T>()); }
到目前為止,我們已經將整個物件池的設計模型進行了完整的介紹,總得來說,這是一個簡單、高效并且具有可擴展性的物件池框架,該模型涉及的幾個核心介面和型別體現在如下圖所示的UML中,

物件池在 .NET (Core)中的應用[1]: 編程篇
物件池在 .NET (Core)中的應用[2]: 設計篇
物件池在 .NET (Core)中的應用[3]: 擴展篇
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/295783.html
標籤:.NET Core
