.NET Core中Object Pool的簡單使用
前言
複用,是一個重要的話題,也是我們日常開發中經常遇到的,不可避免的問題。
舉個最為簡單,大家最為熟悉的例子,資料庫連線池,就是複用資料庫連線。
那麼複用的意義在那裡呢?
簡單來說就是減少不必要的資源損耗。
除了資料庫連線,可能在不同的情景或需求下,還會有很多其他物件需要進行復用,這個時候就會有所謂的 Object Pool(物件池)。
小夥伴們應該也自己實現過類似的功能,或用ConcurrentBag,或用ConcurrentQueue,或用其他方案。
這也裡分享一個在微軟文件中的實現
當然,在.NET Core中,微軟已經幫我們實現了一個簡單的Object Pool。
我們只需要新增Microsoft.Extensions.ObjectPool
Microsoft.Extensions.ObjectPool
Microsoft.Extensions.ObjectPool可以說是.NET Core的一個基礎類庫。
它位於aspnet的Common專案中,型別其他基礎模組都有使用相關的功能,也好比Routing專案。
下面就簡單看看它的用法。
在開始之前,我們先定義一個可以複用的object
public class Demo { public int Id { get; set; } public string Name { get; set; } public DateTime CreateTimte { get; set; } }
用法1
var defalutPolicy = new DefaultPooledObjectPolicy<Demo>(); //最大可保留物件數量 = Environment.ProcessorCount * 2 var defaultPool = new DefaultObjectPool<Demo>(defalutPolicy); for (int i = 0; i < PoolSize; i++) { item1 = defaultPool.Get(); Console.WriteLine($"#{i+1}-{item1.Id}-{item1.Name}-{item1.CreateTimte}"); }
在建立pool之前,我們要先定義一個Policy。這裡直接用自帶的DefaultPooledObjectPolicy來構造。
物件池會有一個維護的最大數量,執行緒數。
通過pool物件的Get
方法,從物件池中取出一個物件。
上面程式碼執行結果
#1-0--01/01/0001 00:00:00
#2-0--01/01/0001 00:00:00
#3-0--01/01/0001 00:00:00
#4-0--01/01/0001 00:00:00
#5-0--01/01/0001 00:00:00
#6-0--01/01/0001 00:00:00
#7-0--01/01/0001 00:00:00
#8-0--01/01/0001 00:00:00
這個結果說明,Object Pool 中的物件都是直接new出來的,並沒有對一些屬性進行貶值操作,這個時候往往沒有太多實際意義。
因為DefaultPooledObjectPolicy本來就是直接new了一個物件出來,很多時候,這並不是我們所期望的!
要想符合我們實際的使用,就要自己定義一個Policy!
下面來看看用法2
用法2
先定義一個Policy,實現 IPooledObjectPolicy 這個介面。T很自然就是我們的Demo類了。
public class DemoPooledObjectPolicy : IPooledObjectPolicy<Demo>
{
public Demo Create()
{
return new Demo { Id = 1, Name = "catcher", CreateTimte = DateTime.Now };
}
public bool Return(Demo obj)
{
return true;
}
}
這裡要實現Create和Return兩個方法。
見名知義,Create方法就是用來建立Demo物件的,Return方法就是將Demo物件扔回Object Pool的(有借有還)。
然後是用DemoPooledObjectPolicy去替換DefaultPooledObjectPolicy。
var demoPolicy = new DemoPooledObjectPolicy();
var defaultPoolWithDemoPolicy = new DefaultObjectPool<Demo>(demoPolicy,1);
//借
item1 = defaultPoolWithDemoPolicy.Get();
//還
defaultPoolWithDemoPolicy.Return(item1);
//借,但是不還
item2 = defaultPoolWithDemoPolicy.Get();
Console.WriteLine($"{item1.Id}-{item1.Name}-{item1.CreateTimte}");
Console.WriteLine($"{item2.Id}-{item2.Name}-{item2.CreateTimte}");
Console.WriteLine(item1 == item2);
//建立一個新的
item3 = defaultPoolWithDemoPolicy.Get();
Console.WriteLine($"{item3.Id}-{item3.Name}-{item3.CreateTimte}");
Console.WriteLine(item3 == item1);
這裡定義了物件池只保留一個物件。
由於從object pool中取出來之後,有一步還回去的操作,所以item1和item2應當是同一個物件。
從object pool中拿出了item2之後,它並沒有還回去,所以object pool會基於我們定義的Policy去建立一個新的物件出來。
下面是用法2的輸出結果:
1-catcher-09/17/2018 22:32:38
1-catcher-09/17/2018 22:32:38
True
1-catcher-09/17/2018 22:32:38
False
可以看到item1,item2和item3的各個屬性是一樣的,並且item1和item2確實是同一個物件。item3和item1並不是同一個。
用法3
除了DefaultObjectPool外,還有DefaultObjectPoolProvider也可以建立一個Object Pool。
建立一個Object Pool,一定是離不開Policy的,所以這裡還是用了我們自己定義的DemoPooledObjectPolicy。
var defaultProvider = new DefaultObjectPoolProvider();
var policy = new DemoPooledObjectPolicy();
//default maximumRetained is Environment.ProcessorCount * 2
ObjectPool<Demo> pool = defaultProvider.Create(policy);
item1 = pool.Get();
pool.Return(item1);
item2 = pool.Get();
Console.WriteLine($"{item1.Id}-{item1.Name}-{item1.CreateTimte}");
Console.WriteLine($"{item2.Id}-{item2.Name}-{item2.CreateTimte}");
Console.WriteLine(item1 == item2);
item3 = pool.Get();
Console.WriteLine($"{item3.Id}-{item3.Name}-{item3.CreateTimte}");
Console.WriteLine(item3 == item2);
用Provider建立Object Pool時,不能指定保留的最大物件數量,只能用的是預設的Environment.ProcessorCount * 2。
後面的使用,和用法2是一樣的。
可以看到item1和item2是同一個物件。從Object Pool中取物件的時候,會取第一個,所以還回去後,再取的話,還是會取到原來的第一個。
item3那裡是直接從Object Pool中取出來的,沒有再次建立,因為這裡的Object Pool維護著多個物件,而不是用法2中的只有一個,所以它是直接從Pool中拿的。
下面是輸出結果
1-catcher-09/17/2018 22:38:34
1-catcher-09/17/2018 22:38:34
True
1-catcher-09/17/2018 22:38:34
False
和用法2,本質是一樣的。
用法4
好像上面的用法,都不那麼像我們正常使用的。我們還是需要依賴注入的。
那麼我們最後就來看看怎麼結合依賴注入吧。當然這裡的本質還是離不開Policy和Provider這兩個東西。
IServiceCollection services = new ServiceCollection();
services.AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();
services.AddSingleton(s =>
{
var provider = s.GetRequiredService<ObjectPoolProvider>();
return provider.Create(new DemoPooledObjectPolicy());
});
ServiceProvider serviceProvider = services.BuildServiceProvider();
var pool = serviceProvider.GetService<ObjectPool<Demo>>();
item1 = pool.Get();
pool.Return(item1);
item2 = pool.Get();
Console.WriteLine($"{item1.Id}-{item1.Name}-{item1.CreateTimte}");
Console.WriteLine($"{item2.Id}-{item2.Name}-{item2.CreateTimte}");
Console.WriteLine(item1 == item2);
item3 = pool.Get();
Console.WriteLine($"{item3.Id}-{item3.Name}-{item3.CreateTimte}");
Console.WriteLine(item3 == item2);
我們首先需要完成對Provider的註冊,然後直接拿它的例項去建立一個Object Pool即可。
如果想在其他地方用,通過建構函式注入即可。
這裡的結果也是和前面一樣的,沒什麼好多說的。
總結
在這幾種用法中,我們最常用的應該是用法4。
但是無論那種用法,我們都需要了解,Object Pool離不開Pool,Policy和Provider這三個傢伙。
有了這三個,或許我們就可以為所欲為了。
當然,它還提供了幾個特殊的東西,有興趣的可以去看看。
LeakTrackingObjectPool
StringBuilderPooledObjectPolicy
最後用一個腦圖結束本文。