借助于有效的自動化垃圾回收機(jī)制,.NET讓開發(fā)人員不在關(guān)心對象的生命周期,但實際上很多性能問題都來源于GC。并不說.NET的GC有什么問題,而是對象生命周期的跟蹤和管理本身是需要成本的,不論交給應(yīng)用還是框架來做,都會對性能造成影響。在一些對性能比較敏感的應(yīng)用中,我們可以通過對象復(fù)用的方式避免垃圾對象的產(chǎn)生,進(jìn)而避免GC因?qū)ο蠡厥諏?dǎo)致的性能損失。對象池是對象復(fù)用的一種常用的方式。.NET提供了一個簡單高效的對象池框架,并使用在ASP.NET自身框架中。這個對象池狂框架由“Microsoft.Extensions.ObjectPool”這個NuGet包提供,我們可以通過添加這個NuGet包它引入我們的應(yīng)用中。接下來我們就通過一些簡單的示例來演示一下對象池的基本編程模式。
一、對象的借與還
和絕大部分的對象池編程方式一樣,當(dāng)我們需要消費某個對象的時候,我們不會直接創(chuàng)建它,而是選擇從對象池中“借出”一個對象。一般來說,如果對象池為空,或者現(xiàn)有的對象都正在被使用,它會自動幫助我們完成對象的創(chuàng)建。借出的對象不再使用的時候,我們需要及時將其“歸還”到對象池中以供后續(xù)復(fù)用。我們在使用.NET的對象池框架時,主要會使用如下這個ObjectPool<T>類型,針對池化對象的借與還體現(xiàn)在它的Get和Return方法中。
public abstract class ObjectPool<T> where T: class { public abstract T Get(); public abstract void Return(T obj); }
我們接下來利用一個簡單的控制臺程序來演示對象池的基本編程模式。在添加了針對“Microsoft.Extensions.ObjectPool”這個NuGet包的引用之后,我們定義了如下這個FoobarService類型來表示希望池化復(fù)用的服務(wù)對象。如代碼片段所示,F(xiàn)oobarService具有一個自增整數(shù)表示Id屬性作為每個實例的唯一標(biāo)識,靜態(tài)字段_latestId標(biāo)識當(dāng)前分發(fā)的最后一個標(biāo)識。
public class FoobarService { internal static int _latestId; public int Id { get; } public FoobarService() => Id = Interlocked.Increment(ref _latestId); }
通過對象池的方式來使用FoobarService對象體現(xiàn)在如下的代碼片段中。我們通過調(diào)用ObjectPool類型的靜態(tài)方法Create<FoobarService>方法得到針對FoobarService類型的對象池,這是一個ObjectPool<FoobarService>對象。針對單個FoobarService對象的使用體現(xiàn)在本地方法ExecuteAsync中。如代碼片段所示,我們調(diào)用ObjectPool<FoobarService>對象的Get方法從對象池中借出一個Foobar對象。為了確定對象是否真的被復(fù)用,我們在控制臺上打印出對象的標(biāo)識。我們通過延遲1秒鐘模擬針對服務(wù)對象的長時間使用,并在最后通過調(diào)用ObjectPool<FoobarService>對象的Return方法將借出的對象釋放到對象池中。
class Program { static async Task Main() { var objectPool = ObjectPool.Create<FoobarService>(); while (true) { Console.Write("Used services: "); await Task.WhenAll(Enumerable.Range(1, 3).Select(_ => ExecuteAsync())); Console.Write("\n"); } async Task ExecuteAsync() { var service = objectPool.Get(); try { Console.Write($"{service.Id}; "); await Task.Delay(1000); } finally { objectPool.Return(service); } } } }
在Main方法中,我們構(gòu)建了一個無限循環(huán),并在每次迭代中并行執(zhí)行ExecuteAsync方法三次。演示實例運行之后會在控制臺上輸出如下所示的結(jié)果,可以看出每輪迭代使用的三個對象都是一樣的。每次迭代,它們從對象池中被借出,使用完之后又回到池中供下一次迭代使用。
二、依賴注入
我們知道依賴注入是已經(jīng)成為 .NET Core的基本編程模式,針對對象池的編程最好也采用這樣的編程方式。如果采用依賴注入,容器提供的并不是代表對象池的ObjectPool<T>對象,而是一個ObjectPoolProvider對象。顧名思義, ObjectPoolProvider對象作為對象池的提供者,用來提供針對指定對象類型的ObjectPool<T>對象。
.NET提供的大部分框架都提供了針對IServiceCollection接口的擴(kuò)展方法來注冊相應(yīng)的服務(wù),但是對象池框架并沒有定義這樣的擴(kuò)展方法,所以我們需要采用原始的方式來完成針對ObjectPoolProvider的注冊。如下面的代碼片段所示,在創(chuàng)建出ServiceCollection對象之后,我們通過調(diào)用AddSingleton擴(kuò)展方法注冊了ObjectPoolProvider的默認(rèn)實現(xiàn)類型DefaultObjectPoolProvider。
class Program { static async Task Main() { var objectPool = new ServiceCollection().AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>() .BuildServiceProvider() .GetRequiredService<ObjectPoolProvider>() .Create<FoobarService>(); … } }
在利用ServiceCollection對象創(chuàng)建出代表依賴注入容器的IServiceProvider對象之后,我們利用它提取出ObjectPoolProvider對象,并通過調(diào)用其Create<T>方法得到表示對象池的ObjectPool<FoobarService>對象。改動的程序執(zhí)行之后同樣會在控制臺輸出如上圖所示的結(jié)果。
三、池化對象策略
通過前面的實例演示可以看出,對象池在默認(rèn)情況下會幫助我們完成對象的創(chuàng)建工作。我們可以想得到,它會在對象池?zé)o可用對象的時候會調(diào)用默認(rèn)的構(gòu)造函數(shù)來創(chuàng)建提供的對象。如果池化對象類型沒有默認(rèn)的構(gòu)造函數(shù)呢?或者我們希望執(zhí)行一些初始化操作呢?
在另一方面,當(dāng)不在使用的對象被歸還到對象池之前,很有可能會執(zhí)行一些釋放性質(zhì)的操作(比如集合對象在歸還之前應(yīng)該被清空)。還有一種可能是對象有可能不能再次復(fù)用(比如它內(nèi)部維護(hù)了一個處于錯誤狀態(tài)并無法恢復(fù)的網(wǎng)絡(luò)連接),那么它就不能被釋放會對象池。上述的這些需求都可以通過IPooledObjectPolicy<T>接口表示的池化對象策略來解決。
同樣以我們演示實例中使用的FoobarService類型,如果并不希望用戶直接調(diào)用構(gòu)造函數(shù)來創(chuàng)建對應(yīng)的實例,所以我們按照如下的方式將其構(gòu)造函數(shù)改為私有,并定義了一個靜態(tài)的工廠方法Create來創(chuàng)建FoobarService對象。當(dāng)FoobarService類型失去了默認(rèn)的無參構(gòu)造函數(shù)之后,我們演示的程序?qū)o法編譯。
public class FoobarService { internal static int _latestId; public int Id { get; } private FoobarService() => Id = Interlocked.Increment(ref _latestId); public static FoobarService Create() => new FoobarService(); }
為了解決這個問題,我們?yōu)镕oobarService類型定義一個代表池化對象策略的FoobarPolicy類型。如代碼片段所示,F(xiàn)oobarPolicy類型實現(xiàn)了IPooledObjectPolicy<FoobarService>接口,實現(xiàn)的Create方法通過調(diào)用FoobarSerivice類型的靜態(tài)同名方法完成針對對象的創(chuàng)建。另一個方法Return可以用來執(zhí)行一些對象歸還前的釋放操作,它的返回值表示該對象還能否回到池中供后續(xù)使用。由于FoobarService對象可以被無限次復(fù)用,所以實現(xiàn)的Return方法直接返回True。
public class FoobarPolicy : IPooledObjectPolicy<FoobarService> { public FoobarService Create() => FoobarService.Create(); public bool Return(FoobarService obj) => true; }
在調(diào)用ObjectPoolProvider對象的Create<T>方法針對指定的類型創(chuàng)建對應(yīng)的對象池的時候,我們將一個IPooledObjectPolicy<T>對象作為參數(shù),創(chuàng)建的對象池將會根據(jù)該對象定義的策略來創(chuàng)建和釋放對象。
class Program { static async Task Main() { var objectPool = new ServiceCollection().AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>() .BuildServiceProvider() .GetRequiredService<ObjectPoolProvider>() .Create(new FoobarPolicy()); … } }
四、對象池的大小
對象池容納對象的數(shù)量總歸是有限的,默認(rèn)情況下它的大小為當(dāng)前機(jī)器處理器數(shù)量的2倍,這一點可以通過一個簡單的實例來驗證一下。如下面的代碼片段所示,我們將演示程序中每次迭代并發(fā)執(zhí)行ExecuteAsync方法的數(shù)量設(shè)置為當(dāng)前機(jī)器處理器數(shù)量的2倍,并將最后一次創(chuàng)建的FoobarService對象的ID打印出來。為了避免控制臺上的無效輸出,我們將ExecuteAsync方法中的控制臺輸出代碼移除。
class Program { static async Task Main() { var objectPool = new ServiceCollection().AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>() .BuildServiceProvider() .GetRequiredService<ObjectPoolProvider>() .Create(new FoobarPolicy()); var poolSize = Environment.ProcessorCount * 2; while (true) { while (true) { await Task.WhenAll(Enumerable.Range(1, poolSize).Select(_ => ExecuteAsync())); Console.WriteLine($"Last service: {FoobarService._latestId}"); } } async Task ExecuteAsync() { var service = objectPool.Get(); try { await Task.Delay(1000); } finally { objectPool.Return(service); } } } }
上面這個演示實例表達(dá)的意思是:對象池的大小和對象消費率剛好是一致的。在這種情況下,消費的每一個對象都是從對象池中提取出來,并且能夠成功還回去,那么對象的創(chuàng)建數(shù)量就是對象池的大小。下圖所示的是演示程序運行之后再控制臺上的輸出結(jié)果,整個應(yīng)用的生命周期范圍內(nèi)一共只會有16個對象被創(chuàng)建出來,因為我當(dāng)前機(jī)器的處理器數(shù)量為8。
如果對象池的大小為當(dāng)前機(jī)器處理器數(shù)量的2倍,那么我們倘若將對象的消費率提高,意味著池化的對象將無法滿足消費需求,新的對象將持續(xù)被創(chuàng)建出來。為了驗證我們的想法,我們按照如下的方式將每次迭代執(zhí)行任務(wù)的數(shù)量加1。
class Program { static async Task Main() { var objectPool = new ServiceCollection().AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>() .BuildServiceProvider() .GetRequiredService<ObjectPoolProvider>() .Create(new FoobarPolicy()); var poolSize = Environment.ProcessorCount * 2; while (true) { while (true) { await Task.WhenAll(Enumerable.Range(1, poolSize + 1) .Select(_ => ExecuteAsync())); Console.WriteLine($"Last service: {FoobarService._latestId}"); } } … } }
再次運行改動后的程序,我們會在控制臺上看到如下圖所示的輸出結(jié)果。由于每次迭代針對對象的需求量是17,但是對象池只能提供16個對象,所以每次迭代都必須額外創(chuàng)建一個新的對象。
五、對象的釋放
由于對象池容納的對象數(shù)量是有限的,如果現(xiàn)有的所有對象已經(jīng)被提取出來,它會提供一個新創(chuàng)建的對象。從另一方面講,我們從對象池得到的對象在不需要的時候總是會還回去,但是對象池可能容不下那么多對象,它只能將其丟棄,被丟棄的對象將最終被GC回收。如果對象類型實現(xiàn)了IDisposable接口,在它不能回到對象池的情況下,它的Dispose方法應(yīng)該被立即執(zhí)行。
為了驗證不能正常回歸對象池的對象能否被及時釋放,我們再次對演示的程序作相應(yīng)的修改。我們讓FoobarService類型實現(xiàn)IDisposable接口,并在實現(xiàn)的Dispose方法中將自身ID輸出到控制臺上。然后我們按照如下的方式以每次迭代并發(fā)量高于對象池大小的方式消費對象。
class Program { static async Task Main() { var objectPool = new ServiceCollection().AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>() .BuildServiceProvider() .GetRequiredService<ObjectPoolProvider>() .Create(new FoobarPolicy()); while (true) { Console.Write("Disposed services:"); await Task.WhenAll(Enumerable.Range(1, Environment.ProcessorCount * 2 + 3).Select(_ => ExecuteAsync())); Console.Write("\n"); } async Task ExecuteAsync() { var service = objectPool.Get(); try { await Task.Delay(1000); } finally { objectPool.Return(service); } } } } public class FoobarService: IDisposable { internal static int _latestId; public int Id { get; } private FoobarService() => Id = Interlocked.Increment(ref _latestId); public static FoobarService Create() => new FoobarService(); public void Dispose() => Console.Write($"{Id}; "); }
演示程序運行之后會在控制臺上輸出如下圖所示的結(jié)果,可以看出對于每次迭代消費的19個對象,只有16個能夠正常回歸對象池,有三個將被丟棄并最終被GC回收。由于這樣的對象將不能被復(fù)用,它的Dispose方法會被調(diào)用,我們定義其中的釋放操作得以被及時執(zhí)行。
.NET Core對象池的應(yīng)用:設(shè)計篇
.NET Core對象池的應(yīng)用:擴(kuò)展篇
到此這篇關(guān)于.NET Core對象池的應(yīng)用:編程篇的文章就介紹到這了,更多相關(guān).NET Core對象池的應(yīng)用內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://www.cnblogs.com/artech/p/object-pool-01.html