背景
行程間通訊屬于老生常談的話題,可能已經有很多的通信示例代碼,但在實際使用中需要做的東西還比較多,例如協議定制、訊息收發、行程管理等都需要實作,進階需求可能還需要實作回呼函式、取消等,
個人在作業中恰好遇到了相關需求,然后在網上搜了一下,只找到兩個.net下多行程相關的庫(有可能是搜索方式不對)
https://github.com/tmds/Tmds.ExecFunction
https://github.com/CyAScott/AppDomainAlternative/
但并不滿足需求,不能行程復用,或者不能方便的進行回呼、取消,于是乎進行手寫相關代碼,
在手寫了兩三個相似的專案后,覺悟到重復的勞動是可以進一步省略的,于是進行了封裝,產生了該專案 - Juxtapose 基于 SourceGenerator 的硬編碼 .Net 多行程運行庫,只需要幾行額外的代碼就能使現有代碼(*)支持多行程運行,行程復用,支持Linux、Windows,支持回呼委托和CancellationToken,支持除錯子行程(*),無顯式的運行時反射呼叫和動態構造類,
好處很多,那么代價是什么呢?
- 簡單的一次方法呼叫將會經歷資料傳輸以及至少四次序列化或反序列化;對比行程內方法呼叫來說是巨大的損耗,
- 方法引數只能是簡單的資料型別(委托和CancellationToken除外,它們進行了特化處理),不能傳遞有行為的物件,
- 跨行程的例外資訊將被包裹,需要自行決議;
- 呼叫堆疊將會變復雜;
綜上,使用時需要能夠接受和處理上述情況,
Juxtapose早期版本已經在生產環境進行了長時間的使用驗證,但新的Release版本暫時還沒有;
相關技術
SourceGenerator
源生成器 - SourceGenerator 已經推出有一段時間了,參見官方公告 Introducing C# Source Generators,
官方簡介:
源生成器是一項 C# 編譯器功能,使 C# 開發人員能夠在編譯用戶代碼時進行檢查,并動態生成新的 C# 源檔案,以添加到用戶的編譯中, 通過這種方式,你的代碼可以在編譯程序中運行并檢查你的程式以生成與其余代碼一起編譯的其他源檔案,
使用SourceGenerator可以在編譯時生成代碼,避免運行時反射,提高運行效率,使FullAOT成為可能,代碼運行邏輯也更加直觀,不在此處進行過多描述,已經有不少的文章對其進行了介紹,可以隨便搜索到,官方也有完善的示例代碼,
相關資源:
- 官方介紹
- 官方指南
- 官方示例
行程間通訊
通訊方式很多,可以參考文章 c#多行程通訊,今天,它來了,
Juxtapose默認實作了命名管道通信(有需求可以自行實作其他方式進行替換),默認使用命名管道的主要原因是它由.net原生集成、支持雙工通信、跨平臺,
使用場景
以下為實際使用場景,更多場景請自行結合實際需求,
- 某組件不允許在一個行程內創建多個實體,無法并發;
- 某組件使用了過多記憶體、記憶體泄漏、記憶體釋放不及時,導致行程被殺、Pod重啟、服務中斷;
- 某組件執行某耗時操作時,不支持取消操作,且占用大量CPU/記憶體資源,影響程式運行;
- 某組件直接操作記憶體,會偶發性出現非法訪問,導致行程退出;
Nuget包介紹
1. Intro
基于 SourceGenerator 的硬編碼 .Net 多行程運行庫,
包串列
| 名稱 | 介紹 |
|---|---|
| Juxtapose | 運行時庫,封裝通用的功能 |
| Juxtapose.SourceGenerator | 源代碼生成器,用于代碼生成 |
| Juxtapose.VsDebugger | VisualStudio除錯附加包,用于子行程除錯 |
2. Features
- 可以為
型別、介面、靜態類生成代理,無需手動撰寫RPC相關代碼,即可多行程運行; - 編譯時生成所有代碼,運行時無顯式的反射呼叫和動態構造;
- 支持
委托和CancellationToken型別的方法引數(其余型別未特殊處理,將會進行序列化,目前回呼委托不支持嵌套和CancellationToken); - 支持
Linux、Windows(其它未測驗); - 支持除錯子行程(
Windows&&VisualStudioOnly);
注意事項
- 目前引數不支持定義為父型別,實際傳遞子型別,序列化時將會按照定義的型別進行序列化和反序列化,會導致具體型別丟失;
- 目前所有的引數都不應該在方法完成后進行保留,
CancellationToken、委托等在方法完成后會被釋放;
3. Requirement
- .Net5.0+(其它版本沒有嘗試過)
4. 使用方法
4.1 參考包
<ItemGroup>
<PackageReference Include="Juxtapose" Version="1.0.0" />
<PackageReference Include="Juxtapose.SourceGenerator" Version="1.0.0" />
</ItemGroup>
4.2 建立背景關系
4.2.1 創建背景關系型別,并使用 [Illusion] 特性指定要生成的型別
[Illusion(typeof(Greeter), typeof(IGreeter), "Juxtapose.Test.GreeterAsIGreeterIllusion")]
public partial class GreeterJuxtaposeContext : JuxtaposeContext
{
}
示例代碼將為Greeter生成IGreeter介面的代理型別Juxtapose.Test.GreeterAsIGreeterIllusion;
Note!!!
- 必須繼承
JuxtaposeContext; - 必須標記
partial關鍵字;
4.2.2 [Illusion] 的多種用法
- 直接為型別生成代理,如下示例生成
Juxtapose.Test.GreeterIllusion型別,且不繼承介面(靜態型別相同用法)
[Illusion(typeof(Greeter))]
- 為型別生成代理,并繼承指定介面,如下示例生成
Juxtapose.Test.GreeterAsIGreeterIllusion型別且繼承IGreeter介面
[Illusion(typeof(Greeter), typeof(IGreeter))]
- 生成型別,并指定型別名稱,如下示例生成
Juxtapose.Test.HelloGreeter型別
[Illusion(typeof(Greeter), generatedTypeName: "Juxtapose.Test.HelloGreeter")]
- 生成從IoC容器獲取的介面代理型別,如下示例生成
Juxtapose.Test.IGreeterIllusionFromIoCContainer型別(此時Context類需要實作IIoCContainerProvider介面,并提供有效的IServiceProvider)
[Illusion(typeof(IGreeterFromServiceProvider), generatedTypeName: "Juxtapose.Test.IGreeterIllusionFromIoCContainer", fromIoCContainer: true)]
4.3 添加入口點
在Main方法開始處添加入口點代碼,并使用指定背景關系
await JuxtaposeEntryPoint.TryAsEndpointAsync(args, GreeterJuxtaposeContext.SharedInstance);
到此已完成開發,創建型別Juxtapose.Test.GreeterAsIGreeterIllusion的物件,并呼叫其方法,其實際邏輯將在子行程中運行;
5. 除錯子行程(Windows&&VisualStudio Only)
5.1 參考除錯包
在Host專案檔案中添加包參考
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<PackageReference Include="Juxtapose.VsDebugger" Version="1.0.0" />
</ItemGroup>
建議和示例代碼一樣,添加條件參考,只在Debug環境下參考除錯包
5.2 添加入口點
在Main方法開始處添加除錯入口點代碼
JuxtaposeDebuggerAttacher.TryAttachToParent(args);
在代碼中打上斷點,運行時將會正確命中斷點(只在 VisualStudio2022 17.0.5 && Win11 21TH2 中進行了測驗,理論上是通用)
6. 作業邏輯
SourceGenerator在編譯時生成代理型別,封裝通信訊息,在創建代理型別物件時,會自動創建子行程,并在子行程中創建目標型別的物件,使用命名管道進行行程間通信,使用System.Text.Json進行訊息的序列化與反序列化,更多技術細節可以參考源代碼,
鏈接
源代碼: https://github.com/stratosblue/juxtapose
Nuget: https://www.nuget.org/packages/Juxtapose
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/421266.html
標籤:.NET技术
