日韩欧美国产精品免费一二-日韩欧美国产精品亚洲二区-日韩欧美国产精品专区-日韩欧美国产另-日韩欧美国产免费看-日韩欧美国产免费看清风阁

LOGO OA教程 ERP教程 模切知識(shí)交流 PMS教程 CRM教程 開(kāi)發(fā)文檔 其他文檔  
 
網(wǎng)站管理員

.NET無(wú)侵入式對(duì)象池解決方案

freeflydom
2024年10月16日 9:41 本文熱度 1133

Pooling(https://github.com/inversionhourglass/Pooling),編譯時(shí)對(duì)象池組件,在編譯時(shí)將指定類型的new操作替換為對(duì)象池操作,簡(jiǎn)化編碼過(guò)程,無(wú)需開(kāi)發(fā)人員手動(dòng)編寫對(duì)象池操作代碼。同時(shí)提供了完全無(wú)侵入式的解決方案,可用作臨時(shí)性能優(yōu)化的解決方案和老久項(xiàng)目性能優(yōu)化的解決方案等。

快速開(kāi)始

引用Pooling.Fody

dotnet add package Pooling.Fody

確保FodyWeavers.xml文件中已配置Pooling,如果當(dāng)前項(xiàng)目沒(méi)有FodyWeavers.xml文件,可以直接編譯項(xiàng)目,會(huì)自動(dòng)生成FodyWeavers.xml文件:

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Pooling /> <!--確保存在Pooling節(jié)點(diǎn)-->
</Weavers>
// 1. 需要池化的類型實(shí)現(xiàn)IPoolItem接口
public class TestItem : IPoolItem
{
    public int Value { get; set; }
    // 當(dāng)對(duì)象返回對(duì)象池化時(shí)通過(guò)該方法進(jìn)行重置實(shí)例狀態(tài)
    public bool TryReset()
    {
        return true;
    }
}
// 2. 在任何地方使用new關(guān)鍵字創(chuàng)建該類型的對(duì)象
public class Test
{
    public void M()
    {
        var random = new Random();
        var item = new TestItem();
        item.Value = random.Next();
        Console.WriteLine(item.Value);
    }
}
// 編譯后代碼
public class Test
{
    public void M()
    {
        TestItem item = null;
        try
        {
            var random = new Random();
            item = Pool<TestItem>.Get();
            item.Value = random.Next();
            Console.WriteLine(item.Value);
        }
        finally
        {
            if (item != null)
            {
                Pool<TestItem>.Return(item);
            }
        }
    }
}

IPoolItem

正如快速開(kāi)始中的代碼所示,實(shí)現(xiàn)了IPoolItem接口的類型便是一個(gè)池化類型,在編譯時(shí)Pooling會(huì)將其new操作替換為對(duì)象池操作,并在finally塊中將池化對(duì)象實(shí)例返還到對(duì)象池中。IPoolItem僅有一個(gè)TryReset方法,該方法用于在對(duì)象返回對(duì)象池時(shí)進(jìn)行狀態(tài)重置,該方法返回false時(shí)表示狀態(tài)重置失敗,此時(shí)該對(duì)象將會(huì)被丟棄。

PoolingExclusiveAttribute

默認(rèn)情況下,實(shí)現(xiàn)IPoolItem的池化類型會(huì)在所有方法中進(jìn)行池化操作,但有時(shí)候我們可能希望該池化類型在部分類型中不進(jìn)行池化操作,比如我們可能會(huì)創(chuàng)建一些池化類型的管理類型或者Builder類型,此時(shí)在池化類型上應(yīng)用PoolingExclusiveAttribute便可指定該池化類型不在某些類型/方法中進(jìn)行池化操作。

[PoolingExclusive(Types = [typeof(TestItemBuilder)], Pattern = "execution(* TestItemManager.*(..))")]
public class TestItem : IPoolItem
{
    public bool TryReset() => true;
}
public class TestItemBuilder
{
    private readonly TestItem _item;
    private TestItemBuilder()
    {
        // 由于通過(guò)PoolingExclusive的Types屬性排除了TestItemBuilder,所以這里不會(huì)替換為對(duì)象池操作
        _item = new TestItem();
    }
    public static TestItemBuilder Create() => new TestItemBuilder();
    public TestItemBuilder SetXxx()
    {
        // ...
        return this;
    }
    public TestItem Build()
    {
        return _item;
    }
}
public class TestItemManager
{
    private TestItem? _cacheItem;
    public void Execute()
    {
        // 由于通過(guò)PoolingExclusive的Pattern屬性排除了TestItemManager下的所有方法,所以這里不會(huì)替換為對(duì)象池操作
        var item = _cacheItem ?? new TestItem();
        // ...
    }
}

如上代碼所示,PoolingExclusiveAttribute有兩個(gè)屬性TypesPatternTypesType類型數(shù)組,當(dāng)前池化類型不會(huì)在數(shù)組中的類型的方法中進(jìn)行池化操作;Patternstring類型AspectN表達(dá)式,可以細(xì)致的匹配到具體的方法(AspectN表達(dá)式格式詳見(jiàn):https://github.com/inversionhourglass/Shared.Cecil.AspectN/blob/master/README.md ),當(dāng)前池化類型不會(huì)在被匹配到的方法中進(jìn)行池化操作。兩個(gè)屬性可以使用其中一個(gè),也可以同時(shí)使用,同時(shí)使用時(shí)將排除兩個(gè)屬性匹配到的所有類型/方法。

NonPooledAttribute

前面介紹了可以通過(guò)PoolingExclusiveAttribute指定當(dāng)前池化對(duì)象在某些類型/方法中不進(jìn)行池化操作,但由于PoolingExclusiveAttribute需要直接應(yīng)用到池化類型上,所以如果你使用了第三方類庫(kù)中的池化類型,此時(shí)你無(wú)法直接將PoolingExclusiveAttribute應(yīng)用到該池化類型上。針對(duì)此類情況,可以使用NonPooledAttribute表明當(dāng)前方法不進(jìn)行池化操作。

public class TestItem1 : IPoolItem
{
    public bool TryReset() => true;
}
public class TestItem2 : IPoolItem
{
    public bool TryReset() => true;
}
public class TestItem3 : IPoolItem
{
    public bool TryReset() => true;
}
public class Test
{
    [NonPooled]
    public void M()
    {
        // 由于方法應(yīng)用了NonPooledAttribute,以下三個(gè)new操作都不會(huì)替換為對(duì)象池操作
        var item1 = new TestItem1();
        var item2 = new TestItem2();
        var item3 = new TestItem3();
    }
}

有的時(shí)候你可能并不是希望方法里所有的池化類型都不進(jìn)行池化操作,此時(shí)可以通過(guò)NonPooledAttribute的兩個(gè)屬性TypesPattern指定不可進(jìn)行池化操作的池化類型。TypesType類型數(shù)組,數(shù)組中的所有類型在當(dāng)前方法中均不可進(jìn)行池化操作;Patternstring類型AspectN類型表達(dá)式,所有匹配的類型在當(dāng)前方法中均不可進(jìn)行池化操作。

public class Test
{
    [NonPooled(Types = [typeof(TestItem1)], Pattern = "*..TestItem3")]
    public void M()
    {
        // TestItem1通過(guò)Types不允許進(jìn)行池化操作,TestItem3通過(guò)Pattern不允許進(jìn)行池化操作,僅TestItem2可進(jìn)行池化操作
        var item1 = new TestItem1();
        var item2 = new TestItem2();
        var item3 = new TestItem3();
    }
}

AspectN類型表達(dá)式靈活多變,支持邏輯非操作符!,所以可以很方便的使用AspectN類型表達(dá)式僅允許某一個(gè)類型,比如上面的示例可以簡(jiǎn)單改為[NonPooled(Pattern = "!TestItem2")],更多AspectN表達(dá)式說(shuō)明,詳見(jiàn):https://github.com/inversionhourglass/Shared.Cecil.AspectN/blob/master/README.md 。

NonPooledAttribute不僅可以應(yīng)用于方法層級(jí),還可以應(yīng)用于類型和程序集。應(yīng)用于類等同于應(yīng)用到類的所有方法上(包括屬性和構(gòu)造方法),應(yīng)用于程序集等同于應(yīng)用到當(dāng)前程序集的所有方法上(包括屬性和構(gòu)造方法),另外如果在應(yīng)用到程序集時(shí)沒(méi)有指定TypesPattern兩個(gè)屬性,那么就等同于當(dāng)前程序集禁用Pooling。

無(wú)侵入式池化操作

看了前面的內(nèi)容再看看標(biāo)題,你可能就在嘀咕“這是哪門子無(wú)侵入式,這不純純標(biāo)題黨”。現(xiàn)在,標(biāo)題的部分來(lái)了。Pooling提供了無(wú)侵入式的接入方式,適用于臨時(shí)性能優(yōu)化和老久項(xiàng)目改造,不需要實(shí)現(xiàn)IPoolItem接口,通過(guò)配置即可指定池化類型。

假設(shè)目前有如下代碼:

namespace A.B.C;
public class Item1
{
    public object? GetAndDelete() => null;
}
public class Item2
{
    public bool Clear() => true;
}
public class Item3 { }
public class Test
{
    public static void M1()
    {
        var item1 = new Item1();
        var item2 = new Item2();
        var item3 = new Item3();
        Console.WriteLine($"{item1}, {item2}, {item3}");
    }
    public static async ValueTask M2()
    {
        var item1 = new Item1();
        var item2 = new Item2();
        await Task.Yield();
        var item3 = new Item3();
        Console.WriteLine($"{item1}, {item2}, {item3}");
    }
}

項(xiàng)目在引用Pooling.Fody后,編譯項(xiàng)目時(shí)項(xiàng)目文件夾下會(huì)生成一個(gè)FodyWeavers.xml文件,我們按下面的示例修改Pooling節(jié)點(diǎn):

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Pooling>
    <Items>
      <Item pattern="A.B.C.Item1.GetAndDelete" />
      <Item pattern="Item2.Clear" inspect="execution(* Test.M1(..))" />
      <Item stateless="*..Item3" not-inspect="method(* Test.M2())" />
	</Items>
  </Pooling>
</Weavers>

上面的配置中,每一個(gè)Item節(jié)點(diǎn)匹配一個(gè)池化類型,上面的配置中展示了全部的四個(gè)屬性,它們的含義分別是:

  • pattern: AspectN類型+方法表達(dá)式。匹配到的類型為池化類型,匹配到的方法為狀態(tài)重置方法(等同于IPoolItem的TryReset方法)。需要注意的是,重置方法必須是無(wú)參的。
  • stateless: AspectN類型表達(dá)式。匹配到的類型為池化類型,該類型為無(wú)狀態(tài)類型,不需要重置操作即可回到對(duì)象池中。
  • inspect: AspectN表達(dá)式。patternstateless匹配到的池化類型,只有在該表達(dá)式匹配到的方法中才會(huì)進(jìn)行池化操作。當(dāng)該配置缺省時(shí)表示匹配當(dāng)前程序集的所有方法。
  • not-inspect: AspectN表達(dá)式。patternstateless匹配到的池化類型不會(huì)在該表達(dá)式匹配到的方法中進(jìn)行池化操作。當(dāng)該配置缺省時(shí)表示不排除任何方法。最終池化類型能夠進(jìn)行池化操作的方法集合為inspect集合與not-inspect集合的差集。

那么通過(guò)上面的配置,Test在編譯后的代碼為:

public class Test
{
    public static void M1()
    {
        Item1 item1 = null;
        Item2 item2 = null;
        Item3 item3 = null;
        try
        {
            item1 = Pool<Item1>.Get();
            item2 = Pool<Item2>.Get();
            item3 = Pool<Item3>.Get();
            Console.WriteLine($"{item1}, {item2}, {item3}");
        }
        finally
        {
            if (item1 != null)
            {
                item1.GetAndDelete();
                Pool<Item1>.Return(item1);
            }
            if (item2 != null)
            {
                if (item2.Clear())
                {
                    Pool<Item2>.Return(item2);
                }
            }
            if (item3 != null)
            {
                Pool<Item3>.Return(item3);
            }
        }
    }
    public static async ValueTask M2()
    {
        Item1 item1 = null;
        try
        {
            item1 = Pool<Item1>.Get();
            var item2 = new Item2();
            await Task.Yield();
            var item3 = new Item3();
            Console.WriteLine($"{item1}, {item2}, {item3}");
        }
        finally
        {
            if (item1 != null)
            {
                item1.GetAndDelete();
                Pool<Item1>.Return(item1);
            }
        }
    }
}

細(xì)心的你可能注意到在M1方法中,item1item2在重置方法的調(diào)用上有所區(qū)別,這是因?yàn)?code style="margin: 0px 3px; padding: 0px 5px; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace, sans-serif; line-height: 1.8; display: inline-block; overflow-x: auto; vertical-align: middle; border-radius: 3px; background-color: rgb(251, 229, 225); color: rgb(192, 52, 29); border: none !important;">Item2的重置方法的返回值類型為bool,Poolinng會(huì)將其結(jié)果作為是否重置成功的依據(jù),對(duì)于void或其他類型的返回值,Pooling將在方法成功返回后默認(rèn)其重置成功。

零侵入式池化操作

看到這個(gè)標(biāo)題是不是有點(diǎn)懵,剛介紹完無(wú)侵入式,怎么又來(lái)個(gè)零侵入式,它們有什么區(qū)別?

在上面介紹的無(wú)侵入式池化操作中,我們不需要改動(dòng)任何C#代碼即可完成指定類型池化操作,但我們?nèi)孕枰砑覲ooling.Fody的NuGet依賴,并且需要修改FodyWeavers.xml進(jìn)行配置,這仍然需要開(kāi)發(fā)人員手動(dòng)操作完成。那如何讓開(kāi)發(fā)人員完全不需要任何操作呢?答案也很簡(jiǎn)單,就是將這一步放到CI流程或發(fā)布流程中完成。是的,零侵入是針對(duì)開(kāi)發(fā)人員的,并不是真的什么都不需要做,而是將引用NuGet和配置FodyWeavers.xml的步驟延后到CI/發(fā)布流程中了。

優(yōu)勢(shì)是什么

類似于對(duì)象池這類型的優(yōu)化往往不是僅僅某一個(gè)項(xiàng)目需要優(yōu)化,這種優(yōu)化可能是普遍性的,那么此時(shí)相比一個(gè)項(xiàng)目一個(gè)項(xiàng)目的修改,統(tǒng)一的在CI流程/發(fā)布流程中配置是更為快速的選擇。另外在面對(duì)一些古董項(xiàng)目時(shí),可能沒(méi)有人愿意去更改任何代碼,即使只是項(xiàng)目文件和FodyWeavers.xml配置文件,此時(shí)也可以通過(guò)修改CI/發(fā)布流程來(lái)完成。當(dāng)然修改統(tǒng)一的CI/發(fā)布流程的影響面可能更廣,這里只是提供一種零侵入式的思路,具體情況還需要結(jié)合實(shí)際情況綜合考慮。

如何實(shí)現(xiàn)

最直接的方式就是在CI構(gòu)建流程或發(fā)布流程中通過(guò)dotnet add package Pooling.Fody為項(xiàng)目添加NuGet依賴,然后將預(yù)先配置好的FodyWeavers.xml復(fù)制到項(xiàng)目目錄下。但如果項(xiàng)目還引用了其他Fody插件,直接覆蓋原有的FodyWeavers.xml可能導(dǎo)致原有的插件無(wú)效。當(dāng)然,你也可以復(fù)雜點(diǎn)通過(guò)腳本控制FodyWeavers.xml的內(nèi)容,這里我推薦一個(gè).NET CLI工具,Cli4Fody可以一步完成NuGet依賴和FodyWeavers.xml配置。

<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
  <Pooling>
    <Items>
      <Item pattern="A.B.C.Item1.GetAndDelete" />
      <Item pattern="Item2.Clear" inspect="execution(* Test.M1(..))" />
      <Item stateless="*..Item3" not-inspect="method(* Test.M2())" />
	</Items>
  </Pooling>
</Weavers>

上面的FodyWeavers.xml,使用Cli4Fody對(duì)應(yīng)的命令為:

fody-cli MySolution.sln \
        --addin Pooling -pv 0.1.0 \
            -n Items:Item -a "pattern=A.B.C.Item1.GetAndDelete" \
            -n Items:Item -a "pattern=Item2.Clear" -a "inspect=execution(* Test.M1(..))" \
            -n Items:Item -a "stateless=*..Item3" -a "not-inspect=method(* Test.M2())"

Cli4Fody的優(yōu)勢(shì)是,NuGet引用和FodyWeavers.xml可以同時(shí)完成,并且Cli4Fody并不會(huì)修改或刪除FodyWeavers.xml中其他Fody插件的配置。更多Cli4Fody相關(guān)配置,詳見(jiàn):https://github.com/inversionhourglass/Cli4Fody

Rougamo零侵入式優(yōu)化案例

肉夾饃(Rougamo),一款靜態(tài)代碼編織的AOP組件。肉夾饃在2.2.0版本中新增了結(jié)構(gòu)體支持,可以通過(guò)結(jié)構(gòu)體優(yōu)化GC。但結(jié)構(gòu)體的使用沒(méi)有類方便,不可繼承父類只能實(shí)現(xiàn)接口,所以很多MoAttribute中的默認(rèn)實(shí)現(xiàn)在定義結(jié)構(gòu)體時(shí)需要重復(fù)實(shí)現(xiàn)。現(xiàn)在,你可以使用Pooling通過(guò)對(duì)象池來(lái)優(yōu)化肉夾饃的GC。在這個(gè)示例中將使用Docker演示如何在Docker構(gòu)建流程中使用Cli4Fody完成零侵入式池化操作:

目錄結(jié)構(gòu):

.
├── Lib
│   └── Lib.csproj                       # 依賴Rougamo.Fody
│   └── TestAttribute.cs                 # 繼承MoAttribute
└── RougamoPoolingConsoleApp
    └── BenchmarkTest.cs
    └── Dockerfile
    └── RougamoPoolingConsoleApp.csproj  # 引用Lib.csproj,沒(méi)有任何Fody插件依賴
    └── Program.cs

該測(cè)試項(xiàng)目在BenchmarkTest.cs里面定義了兩個(gè)空的測(cè)試方法MN,兩個(gè)方法都應(yīng)用了TestAttribute。本次測(cè)試將在Docker的構(gòu)建步驟中使用Cli4Fody為項(xiàng)目增加Pooling.Fody依賴并將TestAttribute配置為池化類型,同時(shí)設(shè)置其只能在TestAttribute.M方法中進(jìn)行池化,然后通過(guò)Benchmark對(duì)比MN的GC情況。

// TestAttribute
public class TestAttribute : MoAttribute
{
    // 為了讓GC效果更明顯,每個(gè)TestAttribute都將持有長(zhǎng)度為1024的字節(jié)數(shù)組
    private readonly byte[] _occupy = new byte[1024];
}
// BenchmarkTest
public class BenchmarkTest
{
    [Benchmark]
    [Test]
    public void M() { }
    [Benchmark]
    [Test]
    public void N() { }
}
// Program
var config = ManualConfig.Create(DefaultConfig.Instance)
    .AddDiagnoser(MemoryDiagnoser.Default);
var _ = BenchmarkRunner.Run<BenchmarkTest>(config);

Dockerfile

FROM mcr.microsoft.com/dotnet/sdk:8.0
WORKDIR /src
COPY . .
ENV PATH="$PATH:/root/.dotnet/tools"
RUN dotnet tool install -g Cli4Fody
RUN fody-cli DockerSample.sln --addin Rougamo -pv 4.0.4 --addin Pooling -pv 0.1.0 -n Items:Item -a "stateless=Rougamo.IMo+" -a "inspect=method(* RougamoPoolingConsoleApp.BenchmarkTest.M(..))"
RUN dotnet restore
RUN dotnet publish "./RougamoPoolingConsoleApp/RougamoPoolingConsoleApp.csproj" -c Release -o /src/bin/publish
WORKDIR /src/bin/publish
ENTRYPOINT ["dotnet", "RougamoPoolingConsoleApp.dll"]

通過(guò)Cli4Fody最終BenchmarkTest.M中織入的TestAttribute進(jìn)行了池化操作,而BenchmarkTest.N中織入的TestAttribute沒(méi)有進(jìn)行池化操作,最終Benchmark結(jié)果如下:

| Method | Mean     | Error   | StdDev   | Gen0   | Gen1   | Allocated |
|------- |---------:|--------:|---------:|-------:|-------:|----------:|
| M      | 188.7 ns | 3.81 ns |  6.67 ns | 0.0210 |      - |     264 B |
| N      | 195.5 ns | 4.09 ns | 11.74 ns | 0.1090 | 0.0002 |    1368 B |

完整示例代碼保存在:https://github.com/inversionhourglass/Pooling/tree/master/samples/DockerSample

在這個(gè)示例中,通過(guò)在Docker的構(gòu)建步驟中使用Cli4Fody完成了對(duì)Rougamo的對(duì)象池優(yōu)化,整個(gè)過(guò)程對(duì)開(kāi)發(fā)時(shí)完全無(wú)感零侵入的。如果你準(zhǔn)備用這種方法對(duì)Rougamo進(jìn)行對(duì)象池優(yōu)化,需要注意的是當(dāng)前示例中的切面類型TestAttribute是無(wú)狀態(tài)的,所以你需要跟開(kāi)發(fā)確認(rèn)所有定義的切面類型都是無(wú)狀態(tài)的,對(duì)于有狀態(tài)的切面類型,你需要定義重置方法并在定義Item節(jié)點(diǎn)時(shí)使用pattern屬性而不是stateless屬性。

在這個(gè)示例中還有一點(diǎn)你可能沒(méi)有注意,只有Lib項(xiàng)目引用了Rougamo.Fody,RougamoPoolingConsoleApp項(xiàng)目并沒(méi)有引用Rougamo.Fody,默認(rèn)情況下應(yīng)用到BenchmarkTestTestAttribute應(yīng)該是不會(huì)生效的,但我這個(gè)例子中卻生效了。這是因?yàn)樵谑褂肅li4Fody時(shí)還指定了Rougamo的相關(guān)參數(shù),Cli4Fody會(huì)為RougamoPoolingConsoleApp添加了Rougamo.Fody引用,所以Cli4Fody也可用于避免遺漏項(xiàng)目隊(duì)Fody插件的直接依賴,更多Cli4Fody的內(nèi)容詳見(jiàn):https://github.com/inversionhourglass/Cli4Fody

配置項(xiàng)

無(wú)侵入式池化操作中介紹了Items節(jié)點(diǎn)配置,除了Items配置項(xiàng)Pooling還提供了其他配置項(xiàng),下面是完整配置示例:

<Pooling enabled="true" composite-accessibility="false">
  <Inspects>
    <Inspect>any_aspectn_pattern</Inspect>
    <Inspect>any_aspectn_pattern</Inspect>
  </Inspects>
  <NotInspects>
    <NotInspect>any_aspectn_pattern</NotInspect>
    <NotInspect>any_aspectn_pattern</NotInspect>
  </NotInspects>
  <Items>
    <Item pattern="method_name_pattern" stateless="type_pattern" inspect="any_aspectn_pattern" not-inspect="any_aspectn_pattern" />
    <Item pattern="method_name_pattern" stateless="type_pattern" inspect="any_aspectn_pattern" not-inspect="any_aspectn_pattern" />
  </Items>
</Pooling>
節(jié)點(diǎn)路徑屬性名稱用途
/Poolingenabled是否啟用Pooling
/Poolingcomposite-accessibilityAspectN是否使用類+方法綜合可訪問(wèn)性進(jìn)行匹配。默認(rèn)僅按方法可訪問(wèn)性進(jìn)行匹配,比如類的可訪問(wèn)性為internal,方法的可訪問(wèn)性為public,那么默認(rèn)情況下該方法的可訪問(wèn)性認(rèn)定為public,將該配置設(shè)置為true后,該方法的可訪問(wèn)性認(rèn)定為internal
/Pooling/Inspects/Inspect[節(jié)點(diǎn)值]AspectN表達(dá)式。
全局篩選器,只有被該表達(dá)式匹配的方法才會(huì)檢查內(nèi)部是否使用到池化類型并進(jìn)行池化操作替換。即使是實(shí)現(xiàn)了IPoolItem的池化類型也會(huì)受限于該配置。
該節(jié)點(diǎn)可配置多條,匹配的方法集合為多條配置的并集。
該節(jié)點(diǎn)缺省時(shí)表示匹配當(dāng)前程序集所有方法。
最終的方法集合是該節(jié)點(diǎn)配置匹配的集合與 /Pooling/NotInspects 配置匹配的集合的差集。
/Pooling/NotInspects/NotInspect[節(jié)點(diǎn)值]AspectN表達(dá)式。
全局篩選器,被該表達(dá)式匹配的方法的內(nèi)部不會(huì)進(jìn)行池化操作替換。即使是實(shí)現(xiàn)了IPoolItem的池化類型也會(huì)受限于該配置。
該節(jié)點(diǎn)可配置多條,匹配的方法集合為多條配置的并集。
該節(jié)點(diǎn)缺省時(shí)表示不排除任何方法。
最終的方法集合是 /Pooling/Inspects 配置匹配的集合與該節(jié)點(diǎn)配置匹配的集合的差集。
/Pooling/Items/ItempatternAspectN類型+方法名表達(dá)式。
匹配的類型會(huì)作為池化類型,匹配的方法會(huì)作為重置方法。
重置方法必須是無(wú)參方法,如果方法返回值類型為bool,返回值還會(huì)被作為是否重置成功的依據(jù)。
該屬性與stateless屬性僅可二選一。
/Pooling/Items/ItemstatelessAspectN類型表達(dá)式。
匹配的類型會(huì)作為池化類型,該類型為無(wú)狀態(tài)類型,在回到對(duì)象池之前不需要進(jìn)行重置。
該屬性與pattern僅可二選一。
/Pooling/Items/IteminspectAspectN表達(dá)式。
patternstateless匹配到的池化類型,只有在該表達(dá)式匹配到的方法中才會(huì)進(jìn)行池化操作。
當(dāng)該配置缺省時(shí)表示匹配當(dāng)前程序集的所有方法。
當(dāng)前池化類型最終能夠應(yīng)用的方法集合為該配置匹配的方法集合與not-inspect配置匹配的方法集合的差集。
/Pooling/Items/Itemnot-inspectAspectN表達(dá)式。
patternstateless匹配到的池化類型不會(huì)在該表達(dá)式匹配到的方法中進(jìn)行池化操作。
當(dāng)該配置缺省時(shí)表示不排除任何方法。
當(dāng)前池化類型最終能夠應(yīng)用的方法集合為inspect配置匹配的方法集合與該配置匹配的方法集合的差集。

可以看到配置中大量使用了AspectN表達(dá)式,了解更多AspectN表達(dá)式的用法詳見(jiàn):https://github.com/inversionhourglass/Shared.Cecil.AspectN/blob/master/README.md

另外需要注意的是,程序集中的所有方法就像是內(nèi)存,而AspectN就像指針,通過(guò)指針操作內(nèi)存時(shí)需格外小心。將預(yù)期外的類型匹配為池化類型可能會(huì)導(dǎo)致同一個(gè)對(duì)象實(shí)例被并發(fā)的使用,所以在使用AspectN表達(dá)式時(shí)盡量使用精確匹配,避免使用模糊匹配。

對(duì)象池配置

對(duì)象池最大對(duì)象持有數(shù)量

每個(gè)池化類型的對(duì)象池最大持有對(duì)象數(shù)量為邏輯處理器數(shù)量乘以2Environment.ProcessorCount * 2,有兩種方式可以修改這一默認(rèn)設(shè)置。

  1. 通過(guò)代碼指定

    通過(guò)Pool.GenericMaximumRetained可以設(shè)置所有池化類型的對(duì)象池最大對(duì)象持有數(shù)量,通過(guò)Pool<T>.MaximumRetained可以設(shè)置指定池化類型的對(duì)象池最大對(duì)象持有數(shù)量。后者優(yōu)先級(jí)高于前者。

  2. 通過(guò)環(huán)境變量指定

    在應(yīng)用啟動(dòng)時(shí)指定環(huán)境變量可以修改對(duì)象池最大持有對(duì)象數(shù)量,NET_POOLING_MAX_RETAIN用于設(shè)置所有池化類型的對(duì)象池最大對(duì)象持有數(shù)量,NET_POOLING_MAX_RETAIN_{PoolItemFullName}用于設(shè)置指定池化類型的對(duì)象池最大對(duì)象持有數(shù)量,其中{PoolItemFullName}為池化類型的全名稱(命名空間.類名),需要注意的是,需要將全名稱中的.替換為_,比如NET_POOLING_MAX_RETAIN_System_Text_StringBuilder。環(huán)境變量的優(yōu)先級(jí)高于代碼指定,推薦使用環(huán)境變量進(jìn)行控制,更為靈活。

自定義對(duì)象池

我們知道官方有一個(gè)對(duì)象池類庫(kù)Microsoft.Extensions.ObjectPool,Pooling沒(méi)有直接引用這個(gè)類庫(kù)而選擇自建對(duì)象池,是因?yàn)镻ooling作為編譯時(shí)組件,對(duì)方法的調(diào)用都是通過(guò)IL直接織入的,如果引用三方類庫(kù),并且三方類庫(kù)在后續(xù)的更新對(duì)方法簽名有所修改,那么可能會(huì)在運(yùn)行時(shí)拋出MethodNotFoundException,所以盡量減少三方依賴是編譯時(shí)組件最好的選擇。

有的朋友可能會(huì)擔(dān)心自建對(duì)象池的性能問(wèn)題,可以放心的是Pooling對(duì)象池的實(shí)現(xiàn)是從Microsoft.Extensions.ObjectPool拷貝而來(lái),同時(shí)精簡(jiǎn)了ObjectPoolProviderPooledObjectPolicy等元素,保持最精簡(jiǎn)的默認(rèn)對(duì)象池實(shí)現(xiàn)。同時(shí),Pooling支持自定義對(duì)象池,實(shí)現(xiàn)IPool接口定義通用對(duì)象池,實(shí)現(xiàn)IPool<T>接口定義特定池化類型的對(duì)象池。下面簡(jiǎn)單演示如何通過(guò)自定義對(duì)象池將對(duì)象池實(shí)現(xiàn)換為Microsoft.Extensions.ObjectPool

// 通用對(duì)象池
public class MicrosoftPool : IPool
{
    private static readonly ConcurrentDictionary<Type, object> _Pools = [];
    public T Get<T>() where T : class, new()
    {
        return GetPool<T>().Get();
    }
    public void Return<T>(T value) where T : class, new()
    {
        GetPool<T>().Return(value);
    }
    private ObjectPool<T> GetPool<T>() where T : class, new()
    {
        return (ObjectPool<T>)_Pools.GetOrAdd(typeof(T), t =>
        {
            var provider = new DefaultObjectPoolProvider();
            var policy = new DefaultPooledObjectPolicy<T>();
            return provider.Create(policy);
        });
    }
}
// 特定池化類型對(duì)象池
public class SpecificalMicrosoftPool<T> : IPool<T> where T : class, new()
{
    private readonly ObjectPool<T> _pool;
    public SpecificalMicrosoftPool()
    {
        var provider = new DefaultObjectPoolProvider();
        var policy = new DefaultPooledObjectPolicy<T>();
        _pool = provider.Create(policy);
    }
    public T Get()
    {
        return _pool.Get();
    }
    public void Return(T value)
    {
        _pool.Return(value);
    }
}
// 替換操作最好在Main入口直接完成,一旦對(duì)象池被使用就不再運(yùn)行進(jìn)行替換操作
// 替換通用對(duì)象池實(shí)現(xiàn)
Pool.Set(new MicrosoftPool());
// 替換特定類型對(duì)象池
Pool<Xyz>.Set(new SpecificalMicrosoftPool<Xyz>());

不僅僅用作對(duì)象池

雖然Pooling的意圖是簡(jiǎn)化對(duì)象池操作和無(wú)侵入式的項(xiàng)目改造優(yōu)化,但得益于Pooling的實(shí)現(xiàn)方式以及提供的自定義對(duì)象池功能,你可以使用Pooling完成的事情不僅僅是對(duì)象池,Pooling的實(shí)現(xiàn)相當(dāng)于在所有無(wú)參構(gòu)造方法調(diào)用的地方埋入了一個(gè)探針,你可以在這里做任何事情,下面簡(jiǎn)單舉幾個(gè)例子。

單例

// 定義單例對(duì)象池
public class SingletonPool<T> : IPool<T> where T : class, new()
{
    private readonly T _value = new();
    public T Get() => _value;
    public void Return(T value) { }
}
// 替換對(duì)象池實(shí)現(xiàn)
Pool<ConcurrentDictionary<Type, object>>.Set(new SingletonPool<ConcurrentDictionary<Type, object>>());
// 通過(guò)配置,將ConcurrentDictionary<Type, object>設(shè)置為池化類型
// <Item stateless="System.Collections.Concurrent.ConcurrentDictionary&lt;System.Type, object&gt;" />

通過(guò)上面的改動(dòng),你成功的讓所有的ConcurrentDictionary<Type, object>>共享一個(gè)實(shí)例。

控制信號(hào)量

// 定義信號(hào)量對(duì)象池
public class SemaphorePool<T> : IPool<T> where T : class, new()
{
    private readonly Semaphore _semaphore = new(3, 3);
    private readonly DefaultPool<T> _pool = new();
    public T Get()
    {
        if (!_semaphore.WaitOne(100)) return null;
        return _pool.Get();
    }
    public void Return(T value)
    {
        _pool.Return(value);
        _semaphore.Release();
    }
}
// 替換對(duì)象池實(shí)現(xiàn)
Pool<Connection>.Set(new SemaphorePool<Connection>());
// 通過(guò)配置,將Connection設(shè)置為池化類型
// <Item stateless="X.Y.Z.Connection" />

在這個(gè)例子中使用信號(hào)量對(duì)象池控制Connection的數(shù)量,對(duì)于一些限流場(chǎng)景非常適用。

線程單例

// 定義現(xiàn)成單例對(duì)象池
public class ThreadLocalPool<T> : IPool<T> where T : class, new()
{
    private readonly ThreadLocal<T> _random = new(() => new());
    public T Get() => _random.Value!;
    public void Return(T value) { }
}
// 替換對(duì)象池實(shí)現(xiàn)
Pool<Random>.Set(new ThreadLocalPool<Random>());
// 通過(guò)配置,將Connection設(shè)置為池化類型
// <Item stateless="System.Random" />

當(dāng)你想通過(guò)單例來(lái)減少GC壓力但對(duì)象又不是線程安全的,此時(shí)便可以ThreadLocal實(shí)現(xiàn)線程內(nèi)單例。

額外的初始化

// 定義現(xiàn)屬性注入對(duì)象池
public class ServiceSetupPool : IPool<Service1>
{
    public Service1 Get()
    {
        var service1 = new Service1();
        var service2 = PinnedScope.ScopedServices?.GetService<Service2>();
        service1.Service2 = service2;
        return service1;
    }
    public void Return(Service1 value) { }
}
// 定義池化類型
public class Service2 { }
[PoolingExclusive(Types = [typeof(ServiceSetupPool)])]
public class Service1 : IPoolItem
{
    public Service2? Service2 { get; set; }
    public bool TryReset() => true;
}
// 替換對(duì)象池實(shí)現(xiàn)
Pool<Service1>.Set(new ServiceSetupPool());

在這個(gè)例子中使用Pooling結(jié)合DependencyInjection.StaticAccessor完成屬性注入,使用相同方式可以完成其他初始化操作。

發(fā)揮想象力

前面的這些例子可能不一定實(shí)用,這些例子的主要目的是啟發(fā)大家開(kāi)拓思路,理解Pooling的基本實(shí)現(xiàn)原理是將臨時(shí)變量的new操作替換為對(duì)象池操作,理解自定義對(duì)象池的可擴(kuò)展性。也許你現(xiàn)在用不上Pooling,但未來(lái)的某個(gè)需求場(chǎng)景下,你可能可以用Pooling快速實(shí)現(xiàn)而不需要大量改動(dòng)代碼。

注意事項(xiàng)

  1. 不要在池化類型的構(gòu)造方法中執(zhí)行復(fù)用時(shí)的初始化操作

    從對(duì)象池中獲取的對(duì)象可能是復(fù)用的對(duì)象,被復(fù)用的對(duì)象是不會(huì)再次執(zhí)行構(gòu)造方法的,所以如果你有一些初始化操作希望每次復(fù)用時(shí)都執(zhí)行,那么你應(yīng)該將該操作獨(dú)立到一個(gè)方法中并在new操作后調(diào)用而不應(yīng)該放在構(gòu)造方法中

    // 修改前池化對(duì)象定義
    public class Connection : IPoolItem
    {
        private readonly Socket _socket;
        public Connection()
        {
            _socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            // 不應(yīng)該在這里Connect,應(yīng)該將Connect操作單獨(dú)獨(dú)立為一個(gè)方法,然后再new操作后調(diào)用
            _socket.Connect("127.0.0.1", 8888);
        }
        public void Write(string message)
        {
            // ...
        }
        public bool TryReset()
        {
            _socket.Disconnect(true);
            return true;
        }
    }
    // 修改前池化對(duì)象使用
    var connection = new Connection();
    connection.Write("message");
    // 修改后池化對(duì)象定義
    public class Connection : IPoolItem
    {
        private readonly Socket _socket;
        public Connection()
        {
            _socket = new(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }
        public void Connect()
        {
            _socket.Connect("127.0.0.1", 8888);
        }
        public void Write(string message)
        {
            // ...
        }
        public bool TryReset()
        {
            _socket.Disconnect(true);
            return true;
        }
    }
    // 修改后池化對(duì)象使用
    var connection = new Connection();
    connection.Connect();
    connection.Write("message");
    
  2. 僅支持將無(wú)參構(gòu)造方法的new操作替換為對(duì)象池操作

    由于復(fù)用的對(duì)象無(wú)法再次執(zhí)行構(gòu)造方法,所以構(gòu)造參數(shù)對(duì)于池化對(duì)象毫無(wú)意義。如果希望通過(guò)構(gòu)造參數(shù)完成一些初始化操作,可以將新建一個(gè)初始化方法接收這些參數(shù)并完成初始化,或通過(guò)屬性接收這些參數(shù)。

    Pooling在編譯時(shí)會(huì)檢查new操作是否調(diào)用了無(wú)參構(gòu)造方法,如果調(diào)用了有參構(gòu)造方法,將不會(huì)將本次new操作替換為對(duì)象池操作。

  3. 注意不要將池化類型實(shí)例進(jìn)行持久化保存

    Pooling的對(duì)象池操作是方法級(jí)別的,也就是池化對(duì)象在當(dāng)前方法中創(chuàng)建也在當(dāng)前方法結(jié)束時(shí)釋放,不可將池化對(duì)象持久化到字段之中,否則會(huì)存在并發(fā)使用的風(fēng)險(xiǎn)。如果池化對(duì)象的聲明周期跨越了多個(gè)方法,那么你應(yīng)該手動(dòng)創(chuàng)建對(duì)象池并手動(dòng)管理該對(duì)象。

    Pooling在編譯時(shí)會(huì)進(jìn)行簡(jiǎn)單的持久化排查,對(duì)于排查出來(lái)的池化對(duì)象將不進(jìn)行池化操作。但需要注意的是,這種排查僅可排查一些簡(jiǎn)單的持久化操作,無(wú)法排查出復(fù)雜情況下的持久化操作,比如你在當(dāng)前方法中調(diào)用另一個(gè)方法傳入了池化對(duì)象實(shí)例,然后在被調(diào)用方法中進(jìn)行持久化操作。所以根本上還是需要你自己注意,避免將池化對(duì)象持久化保存。

  4. 需要編譯時(shí)進(jìn)行對(duì)象池操作替換的程序集都需要引用Pooling.Fody

    Pooling的原理是在編譯時(shí)檢查所有方法(也可以通過(guò)配置選擇部分方法)的MSIL,排查所有newobj操作完成對(duì)象池替換操作,觸發(fā)該操作是通過(guò)Fody添加了一個(gè)MSBuild任務(wù)完成的,而只有當(dāng)前程序集直接引用了Fody才能夠完成添加MSBuild任務(wù)這一操作。Pooling.Fody通過(guò)一些配置使得直接引用Pooling.Fody也可完成添加MSBuild任務(wù)的操作。

  5. 多個(gè)Fody插件同時(shí)使用時(shí)的注意事項(xiàng)

    當(dāng)項(xiàng)目引用了一個(gè)Fody插件時(shí),在編譯時(shí)會(huì)自動(dòng)生成一個(gè)FodyWeavers.xml文件,如果在FodyWeavers.xml文件已存在的情況下再引用一個(gè)其他Fody插件,此時(shí)再編譯,新的插件將不會(huì)追加到FodyWeavers.xml文件中,需要手動(dòng)配置。同時(shí)在引用多個(gè)Fody插件時(shí)需要注意他們?cè)?code style="margin: 0px 3px; padding: 0px 5px; font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace, sans-serif; line-height: 1.8; display: inline-block; overflow-x: auto; vertical-align: middle; border-radius: 3px; background-color: rgb(251, 229, 225); color: rgb(192, 52, 29); border: none !important;">FodyWeavers.xml中的順序,FodyWeavers.xml順序?qū)?yīng)著插件執(zhí)行順序,部分Fody插件可能存在功能交叉,不同的順序可能產(chǎn)生不同的效果。

AspectN

在文章的最后再提一下AspectN,之前一直稱其為AspectJ-Like表達(dá)式,因?yàn)榇_實(shí)是參照AspectJ表達(dá)式的格式設(shè)計(jì)的,不過(guò)一直這么叫也不是辦法,現(xiàn)在按照慣例更名為AspectN表達(dá)式(搜了一下,.NET里面沒(méi)有這個(gè)名詞,應(yīng)該不存在沖突)。AspectN最早起源于肉夾饃2.0,用于提供更加精確的切入點(diǎn)匹配,現(xiàn)在再次投入到Pooling中使用。

在使用Fody或直接使用Mono.Cecil開(kāi)發(fā)MSBuild任務(wù)插件時(shí),如何查找到需要修改的類型或方法永遠(yuǎn)是首要任務(wù)。最常用的方式便是通過(guò)類型和方法上的Attribute元數(shù)據(jù)進(jìn)行定位,但這樣做基本確定了必須要修改代碼來(lái)添加Attribute應(yīng)用,這是侵入性的。AspectN提供了非侵入式的類型和方法匹配機(jī)制,字符串可承載的無(wú)窮信息給予了AspectN無(wú)限的精細(xì)化匹配可能。很多Fody插件都可以借助AspectN實(shí)現(xiàn)無(wú)侵入式代碼織入,比如ConfigureAwait.Fody,可以使用AspectN實(shí)現(xiàn)通過(guò)配置指定哪些類型或方法需要應(yīng)用ConfigureAwait,哪些不需要。

AspectN不依賴于Fody,僅依賴于Mono.Cecil,如果你有在使用Fody或Mono.Cecil,或許可以嘗試一下AspectN(https://github.com/inversionhourglass/Shared.Cecil.AspectN)。AspectN是一個(gè)共享項(xiàng)目(Shared Project),沒(méi)有發(fā)布NuGet,也沒(méi)有依賴具體Mono.Cecil的版本,使用AspectN你需要將AspectN克隆到本地作為共享項(xiàng)目直接引用,如果你的項(xiàng)目使用git進(jìn)行管理,那么推薦將AspectN作為一個(gè)submodule添加到你的倉(cāng)庫(kù)中(可以參考RougamoPooling)。

轉(zhuǎn)自https://www.cnblogs.com/nigture/p/18468831


該文章在 2024/10/16 9:43:48 編輯過(guò)
關(guān)鍵字查詢
相關(guān)文章
正在查詢...
點(diǎn)晴ERP是一款針對(duì)中小制造業(yè)的專業(yè)生產(chǎn)管理軟件系統(tǒng),系統(tǒng)成熟度和易用性得到了國(guó)內(nèi)大量中小企業(yè)的青睞。
點(diǎn)晴PMS碼頭管理系統(tǒng)主要針對(duì)港口碼頭集裝箱與散貨日常運(yùn)作、調(diào)度、堆場(chǎng)、車隊(duì)、財(cái)務(wù)費(fèi)用、相關(guān)報(bào)表等業(yè)務(wù)管理,結(jié)合碼頭的業(yè)務(wù)特點(diǎn),圍繞調(diào)度、堆場(chǎng)作業(yè)而開(kāi)發(fā)的。集技術(shù)的先進(jìn)性、管理的有效性于一體,是物流碼頭及其他港口類企業(yè)的高效ERP管理信息系統(tǒng)。
點(diǎn)晴WMS倉(cāng)儲(chǔ)管理系統(tǒng)提供了貨物產(chǎn)品管理,銷售管理,采購(gòu)管理,倉(cāng)儲(chǔ)管理,倉(cāng)庫(kù)管理,保質(zhì)期管理,貨位管理,庫(kù)位管理,生產(chǎn)管理,WMS管理系統(tǒng),標(biāo)簽打印,條形碼,二維碼管理,批號(hào)管理軟件。
點(diǎn)晴免費(fèi)OA是一款軟件和通用服務(wù)都免費(fèi),不限功能、不限時(shí)間、不限用戶的免費(fèi)OA協(xié)同辦公管理系統(tǒng)。
Copyright 2010-2025 ClickSun All Rights Reserved

主站蜘蛛池模板: 国产亚洲精品自拍 | 国产在线观看福利一区二区 | 片在线播放 | 人成视频播放 | 亚洲国产综合一区日韩精品 | 欧美性播放中国 | 在线精品亚洲一区二区绿巨人 | 热99re6久精品国产首页青柠 | 91欧洲在线视精品在亚洲 | 欧美一级高清视频在线播放 | 国产亚洲欧美日韩精品一区二区 | 国产精品激情欧美可乐视频 | 97色伦色在线综合视频 | 一级a爱片免费观看高清完整 | 国产欧美一区二区三区户外 | 国产日产亚洲系列电影 | 免费在线观看的网站 | 国产精品4p露脸在线播放 | 精品国产欧美一区二区 | 欧美日韩亚洲一区二区精品 | 国产精品一区二区国产 | 巨臀中文字幕一区二区 | 在线观看午夜亚洲一区 | 日本高清免费aaaaa大片视频 | 最新国产一区二区三区在线 | 国产在线一区二区三区不卡 | 欧美一区视频在线 | 麻花星空影视传 | 傲盟下载| 欧美人与日本人xx在线视频 | 亚洲欧美精品日韩片 | 欧洲vodafon | 日本免费一区二区视频 | 91国语精品自产拍在 | 免费国产黄线在线观 | 日本韩国三级aⅴ在线观看 老妇小说 | 国产成在线观看免费视频成本 | 国产精品成人a在线观看 | 老师脱了内裤让我爽了一夜 | 亚洲中字第 | 国产伦理一区二区 |