主頁 > .NET開發 > 物件池在 .NET (Core)中的應用[2]: 設計篇

物件池在 .NET (Core)中的應用[2]: 設計篇

2021-08-31 15:17:29 .NET開發

《編程篇》已經涉及到了物件池模型的大部分核心介面和型別,物件池模型其實是很簡單的,不過其中有一些為了提升性能而刻意為之的實作細節倒是值得我們關注,總的來說,物件池模型由三個核心物件構成,它們分別是表示物件池的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個物件,

image

如下面的代碼片段所示,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中,

d

物件池在 .NET (Core)中的應用[1]: 編程篇
物件池在 .NET (Core)中的應用[2]: 設計篇
物件池在 .NET (Core)中的應用[3]: 擴展篇

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/295783.html

標籤:.NET Core

上一篇:ASP.NET Core MVC基礎知識

下一篇:.Net Core with 微服務 - 分布式事務 - TCC

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more