.NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试
2023年11月15日,對.net的開發圈是一個重大的日子,.net 8.0正式版發布。
圈內已經預熱了有半個月有余,性能不斷超越,開發體驗越來越完美,早在.net 5.0的時候就各種吹風Aot編譯,直到6.0 7.0使用仍然比較麻煩,我個人比較期待本次更新的aot體驗。
有的群友幾個小時都等不了啦,半夜就開始更新預覽版,我是等到第二天早上正式布發布才開始的,開機第一件事情下載.net8.0 SDK,隨后更新vs2022企業版。
支持開源
我是開源人:https://github.com/2881099
本文通過我們的開源項目升級,以及AOT試驗,記錄了整個經驗過程。
使用我們開源項目的朋友一般都知道,特點依賴較少(甚至零依賴),每次 .net 新版本發布很輕松就支持了,并且為 AOT 埋下了種子。
第一個要更新的開源項目是FreeRedis,這個項目沒有任何外部依賴,本身是支持.net 8.0的,本次維護主要把和測試有關項目類型修改成.net8.0,前后只花了大概十分鐘,跑完測試后發布了 FreeRedis 1.2.5
FreeRedis 是 .NETFramework 4.0 及以上 訪問 redis-server 的客戶端組件
第二個要更新的開源項目是CSRedisCore,大致步驟同上,目前這個項目處于穩定維護階段,不再增加新功能。
CSRedisCore 是 .NETFramework 4.0 及以上 訪問 redis-server 的客戶端組件,也是 FreeSql 作者早年發布的 nuget 版本
第三個要更新的開源項目是FreeSql,這個項目比較龐大,解決方案內有50個子項目,由于主項目也是零依賴,所以基本不需要修改就支持.net8.0。最新編譯器提示.netcoreapp2.1高風險漏洞的警告,不得已移除了.netcoreapp2.1有關的依賴注入支持,前后大約花了半個小時,測試后發布了 FreeSql 3.2.805
FreeSql 是一款功能強大的對象關系映射(O/RM)組件,支持 .NET Core 2.1+、.NET Framework 4.0+ 以及 Xamarin?
第四個要更新的項目是FreeScheduler,這是一個純凈版的定時任務框架,依賴較少只花了5分鐘測試發布。
FreeScheduler 實現輕量化定時任務調度,支持集群、臨時的延時任務和重復循環任務(可持久化),可按秒,每天/每周/每月固定時間,自定義間隔執行,支持 .NET Core 2.1+、.NET Framework 4.0+ 運行環境。
其他幾個開源項目穩定且不依賴 .net 版本,所以本次無需維護更新。
測試與支持 FreeRedis aot
下午沒事去買了一杯咖啡,到12點鐘還睡不著,刷視頻刷到一點半還是睡不著,于是想折騰點什么東西,正好.net 8.0 aot特性,測試一下FreeRedis,看看是否支持。
我是直接創建控制臺程序測試的,設置成aot發布之后,.csproj 內容如下:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PublishAot>True</PublishAot>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="FreeRedis" Version="1.2.5" />
</ItemGroup>
</Project>
發布 aot 需要使用控制臺命令:
dotnet publish -r win-x64 -c release
第一次發布失敗,提示要安裝桌面版C++,于是我重新去官網下載vS2022企業版安裝器,運行它點擊修改安裝,選中桌面版C++進行安裝,大概過了15分鐘,安裝完畢。
E:\github\FreeRedis\examples\console_net8>dotnet publish -r win-x64 -c release
適用于 .NET MSBuild 版本 17.8.3+195e7f5a3
正在確定要還原的項目…
所有項目均是最新的,無法還原。
console_net8 -> E:\github\FreeRedis\examples\console_net8\bin\release\net8.0\win-x64\console_net8.dll
Generating native code
C:\Users\28810\.nuget\packages\freeredis\1.2.5\lib\netstandard2.0\FreeRedis.dll : warning IL3053: Assembly 'FreeRedis'
produced AOT analysis warnings. [E:\github\FreeRedis\examples\console_net8\console_net8.csproj]
C:\Users\28810\.nuget\packages\freeredis\1.2.5\lib\netstandard2.0\FreeRedis.dll : warning IL2104: Assembly 'FreeRedis'
produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries [E:\github\FreeRedis\examples\c
onsole_net8\console_net8.csproj]
console_net8 -> E:\github\FreeRedis\examples\console_net8\bin\release\net8.0\win-x64\publish\
只要編譯成功,發布aot必然會成功,只是會有一些警告。
2023/11/16 13:06 5,637,120 console_net8.exe
2023/11/16 13:06 137,695,232 console_net8.pdb
2023/11/16 04:13 127,268 FreeRedis.pdb
請無視 .pdb 文件,它是調試用途的可以刪除,console_net8.exe 只有 5兆大小。
第二次發布后,運行成功了,純字符串數值之內的操作全部成功。
正當得意之時,redis.AclGetUser 方法拋出了一個新的錯誤,該方法返回的是一個實體類型 AclGetUserResult,有使用 Activetor.CreateInstance(typeof(AclGetUserResult)),Aot本身是支持這個方法的,錯誤提示是不支持該方法的對象類型 AclUserResult。
Unhandled Exception: System.MissingMethodException: No parameterless constructor defined for type 'FreeRedis.AclGetUserResult'.
at System.ActivatorImplementation.CreateInstance(Type, Boolean) + 0x119
at FreeRedis.RespHelper.CreateInstanceGetDefaultValue(Type) + 0x120
at FreeRedis.RespHelper.MapToClass[T](Object[], Encoding) + 0x4a
at FreeRedis.RedisClient.<>c__DisplayClass457_0.<AclGetUser>b__1(Object[] a, Boolean _) + 0x220
at FreeRedis.RedisResult.ThrowOrValue[TValue](Func`3) + 0x58
at FreeRedis.RedisClient.PoolingAdapter.<>c__DisplayClass9_0`1.<AdapterCall>b__0() + 0x141
at FreeRedis.RedisClient.LogCallCtrl[T](CommandPacket cmd, Func`1 func, Boolean aopBefore, Boolean aopAfter) + 0x3bb
at FreeRedis.RedisClient.LogCall[T](CommandPacket cmd, Func`1 func) + 0x63
at FreeRedis.RedisClient.PoolingAdapter.AdapterCall[TValue](CommandPacket, Func`2) + 0x9a
at console_net8.Program.Main(String[] args) + 0xa4
at console_net8!<BaseAddress>+0x2c67f0
于是我系統的去看了官方aot文檔,發現文檔太過于簡陋,反復看了七八遍也沒有找到相關的解決內容。不得已擴大了搜索范圍,在谷歌搜索關鍵字 .net aot 花了近一個小時,最終定位的關鍵字是 rd.xml,配置相當簡單,只需要把FreeRedis的類型全部配置即可。
對應的 .csproj 內容如下:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<PublishAot>True</PublishAot>
</PropertyGroup>
<ItemGroup>
<RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="FreeRedis" Version="1.2.5" />
</ItemGroup>
</Project>
對應的 rd.xml 內容如下:
<Directives>
<Application>
<Assembly Name="FreeRedis" Dynamic="Required All">
</Assembly>
</Application>
</Directives>
重新發布后,完美的解決所有問題,.exe 文件體積增漲到 7兆。
這多虧當初設計FreeRedis的時候把依賴簡單最低,才這么容易支持更多的運行平臺。
第二輪aot試驗 FreeScheduler
FreeRedis對.net 8.0以及aot的支持完美收官,這個時候已經凌晨三點,咖啡的勁還很足。
我本身對FreeSql Aot是不抱希望的,所以,就去測試FreeScheduler了。
FreeScheduler支持三種存儲方式,內存/數據庫/redis
基于內存,毫無壓力,直接通過測試。(得益于依賴較少)
基于redis,由于FreeRdis通過了aot測試,基本不會有太大的問題,記得設置好rd.xml,順利通過。
對應的 .csproj 如下:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>true</InvariantGlobalization>
<PublishAot>true</PublishAot>
</PropertyGroup>
<ItemGroup>
<RdXmlFile Include="rd.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\FreeScheduler\FreeScheduler.csproj" />
</ItemGroup>
</Project>
對應的 rd.xml 內容如下:
<Directives>
<Application>
<Assembly Name="FreeScheduler" Dynamic="Required All">
</Assembly>
<Assembly Name="FreeRedis" Dynamic="Required All">
</Assembly>
</Application>
</Directives>
FreeScheduler還有一個web管理面板功能,抱著嘗試的態度試一試,創建.net8.0自帶的web API aot項目,把有關代碼加到項目的運行,居然能直接通過,太牛逼了,這是我沒有想到的。
對應 Program.cs
using FreeRedis;
using FreeScheduler;
using Newtonsoft.Json;
var redis = new RedisClient("127.0.0.1,poolsize=10,exitAutoDisposePool=false");
redis.Serialize = obj => JsonConvert.SerializeObject(obj);
redis.Deserialize = (json, type) => JsonConvert.DeserializeObject(json, type);
redis.Notice += (s, e) =>
{
if (e.Exception != null)
Console.WriteLine(e.Log);
};
Scheduler scheduler = new FreeSchedulerBuilder()
.OnExecuting(task =>
{
Console.WriteLine($"[{DateTime.Now.ToString("HH:mm:ss.fff")}] {task.Topic} 被執行");
task.Remark("log..");
})
.UseStorage(redis)
.Build();
if (Datafeed.GetPage(scheduler, null, null, null, null).Total == 0)
{
scheduler.AddTask("[系統預留]清理任務數據", "86400", -1, 3600);
scheduler.AddTaskRunOnWeek("(周一)武林大會", "json", -1, "1:12:00:00");
scheduler.AddTaskRunOnWeek("(周日)親子活動", "json", -1, "0:00:00:00");
scheduler.AddTaskRunOnWeek("(周六)社交活動", "json", -1, "6:00:00:00");
scheduler.AddTaskRunOnMonth("月尾最后一天", "json", -1, "-1:16:00:00");
scheduler.AddTaskRunOnMonth("月初第一天", "json", -1, "1:00:00:00");
scheduler.AddTask("定時20秒", "json", 10, 20);
scheduler.AddTask("測試任務1", "json", new[] { 10, 30, 60, 100, 150, 200 });
}
var builder = WebApplication.CreateSlimBuilder(args);
builder.Services.AddSingleton(scheduler);
var app = builder.Build();
var applicationLifeTime = app.Services.GetService<IHostApplicationLifetime>();
applicationLifeTime.ApplicationStopping.Register(() =>
{
scheduler.Dispose();
redis.Dispose();
});
app.UseFreeSchedulerUI("/freescheduler/");
app.Run();
2023/11/16 04:23 127 appsettings.Development.json
2023/11/16 04:23 151 appsettings.json
2023/11/16 13:34 25,104,384 Examples_FreeScheduler_Net80_aot.exe
2023/11/16 13:34 238,948,352 Examples_FreeScheduler_Net80_aot.pdb
2023/11/16 13:31 31,208 FreeScheduler.pdb
Examples_FreeScheduler_Net80_aot.exe 25兆,流弊了,雙擊運行它吧~~~~
打開瀏覽器訪問:http://localhost:5000/freescheduler/
意想不到,連管理面板都支持 AOT,這讓我有了繼續試驗的動力~~~
aot試驗意外收獲 FreeSql
四點了,還沒犯困!
最后抱著必涼的心態嘗試終極試驗,FreeScheduler使用數據庫持久化。
第一次失敗,報錯在FreeSql內部,這是是有TaskInterval類型不存在,它其實是FreeScheduler程序集的,并且rd.xml已經配置好了,反復折騰仍然報錯。
Unhandled Exception: System.NotSupportedException: 'FreeScheduler.TaskInterval[]' is missing native code or metadata. This can happen for code that is not compatible with trimming or AOT. Inspect and fix trimming and AOT related warnings that were generated when the app was published. For more information see https://aka.ms/nativeaot-compatibility
at System.Reflection.Runtime.General.TypeUnifier.WithVerifiedTypeHandle(RuntimeArrayTypeInfo, RuntimeTypeInfo) + 0x54
at System.Array.InternalCreate(RuntimeType elementType, Int32 rank, Int32* pLengths, Int32* pLowerBounds) + 0x64
at System.Array.CreateInstance(Type elementType, Int32 length) + 0x46
at System.RuntimeType.GetEnumValues() + 0x53
at FreeSql.Internal.Utils.GetTableByEntity(Type entity, CommonUtils common) + 0x138a
at FreeSql.Internal.CommonProvider.CodeFirstProvider.<SyncStructure>b__51_0(CodeFirstProvider.TypeAndName a) + 0x6e
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext() + 0x3f
at System.Linq.Enumerable.WhereEnumerableIterator`1.ToArray() + 0x5c
at FreeSql.Internal.CommonProvider.CodeFirstProvider.SyncStructure(CodeFirstProvider.TypeAndName[] objects) + 0xd6
at FreeSql.Internal.CommonProvider.CodeFirstProvider.SyncStructure[TEntity]() + 0x6c
at FreeScheduler.TaskHandlers.FreeSqlHandler..ctor(IFreeSql fsql) + 0xf0
at FreeSchedulerBuilder.Build() + 0x2f
at Program.<Main>$(String[] args) + 0x180
at Examples_FreeScheduler_Net80_aot!<BaseAddress>+0xca7fc3
從堆棧 GetEnumValues 可以看出是執行 Enum.GetValues 報錯,通過不斷嘗試中的其中一次,在程序開始寫了一行:
Console.WriteLine(Enum.GetValues(typeof(TaskInterval)));
重新發布后,又出現另一個錯誤,大致與上面的相同,只是類型變成了 TaskStatus,同樣加上一行代碼:
Console.WriteLine(Enum.GetValues(typeof(TaskStatus)));
又出現了另一個錯誤:
Unhandled Exception: System.InvalidOperationException: The binary operator Equal is not defined for the types 'System.Reflection.Runtime.TypeInfos.NativeFormat.NativeFormatRuntimeNamedTypeInfo' and 'System.Reflection.Runtime.TypeInfos.RuntimeConstructedGenericTypeInfo'.
at System.Linq.Expressions.Expression.GetEqualityComparisonOperator(ExpressionType, String, Expression, Expression, Boolean) + 0x26b
at System.Linq.Expressions.Expression.Equal(Expression, Expression, Boolean, MethodInfo) + 0x63
at FreeSql.Internal.Utils.<GetDataReaderValueBlockExpression>g__LocalFuncGetExpression|65_0(Boolean ignoreArray, Utils.<>c__DisplayClass65_0&) + 0x3916
at FreeSql.Internal.Utils.GetDataReaderValueBlockExpression(Type type, Expression value) + 0x18d
at FreeSql.Internal.Utils.<>c__DisplayClass66_0.<GetDataReaderValue>b__1(Type valueType2) + 0x68
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey, Func`2) + 0xa4
at FreeSql.Internal.Utils.GetDataReaderValue(Type type, Object value) + 0x147
at FreeSql.Internal.Utils.GetTableByEntity(Type entity, CommonUtils common) + 0x145c
at FreeSql.Internal.CommonProvider.CodeFirstProvider.<SyncStructure>b__51_0(CodeFirstProvider.TypeAndName a) + 0x6e
at System.Linq.Enumerable.WhereSelectArrayIterator`2.MoveNext() + 0x3f
at System.Linq.Enumerable.WhereEnumerableIterator`1.ToArray() + 0x5c
at FreeSql.Internal.CommonProvider.CodeFirstProvider.SyncStructure(CodeFirstProvider.TypeAndName[] objects) + 0xd6
at FreeSql.Internal.CommonProvider.CodeFirstProvider.SyncStructure[TEntity]() + 0x6c
at FreeScheduler.TaskHandlers.FreeSqlHandler..ctor(IFreeSql fsql) + 0xf0
at FreeSchedulerBuilder.Build() + 0x2f
at Program.<Main>$(String[] args) + 0x1a8
at Examples_FreeScheduler_Net80_aot!<BaseAddress>+0xca8423
這次使用 vs2022 附加進程的方式進行了調試,深入 FreeSql 內部源碼(表達式樹)環境,定位到了一行代碼:
Expression.Equal(type, Expression.Contrast(Guid?))
把 Guid? 后面的問題去掉后,再次發布。(由于每次發布時間要20-30秒,重試時間成本太高,卡在這個問題已經有半個小時)
2023/11/16 04:23 127 appsettings.Development.json
2023/11/16 04:23 151 appsettings.json
2023/11/16 13:52 29,018,112 Examples_FreeScheduler_Net80_aot.exe
2023/11/16 13:52 238,948,352 Examples_FreeScheduler_Net80_aot.pdb
2023/11/16 13:31 31,208 FreeScheduler.pdb
2021/11/03 01:47 1,763,632 SQLite.Interop.dll
對于一個 web 項目并且包含 bootstrap 有關靜態資源文件,.exe 文件只有 29兆太滿意了
看到控制臺上的 SQL,太驚喜了,成功啦~~~~
最后建議
從 .net6.0 到 .net8.0,我們肉眼看不到變化,實際微軟做了很多內部工作,在 aot 使用體驗上明顯能感知。
有人說信創國產運行,那現在 aot 算什么?
.net8.0 AOT 已經到了可用的階段,期待未來版本能改進以下問題:
- 發布速度變快,目前20-30秒一次實在太慢
- 編譯前檢查錯誤,而不是等發布后再報運行時錯誤
- 加強調試,.pdb 100兆++ 為何調試還都是 c++ 有關內容,不能白瞎了這么大的調試文件啊
- 盡快修復 Console.WriteLine(Enum.GetValues(typeof(TaskInterval))) 這個問題
我是開源人:https://github.com/2881099
Native AOT apps have the following limitations:
- No dynamic loading, for example, Assembly.LoadFile.
- No run-time code generation, for example, System.Reflection.Emit.
- No C++/CLI.
- Windows: No built-in COM.
- Requires trimming, which has limitations.
- Implies compilation into a single file, which has known incompatibilities.
- Apps include required runtime libraries (just like self-contained apps, increasing their size as compared to framework-dependent apps).
- System.Linq.Expressions always use their interpreted form, which is slower than run-time generated compiled code.
- Not all the runtime libraries are fully annotated to be Native AOT compatible. That is, some warnings in the runtime libraries aren't actionable by end developers.
總結
以上是生活随笔為你收集整理的.NET8.0 AOT 经验分享 FreeSql/FreeRedis/FreeScheduler 均已通过测试的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 文心一言 VS 讯飞星火 VS chat
- 下一篇: 如何通过 wireshark 捕获 C#