大家好,先祝大家國慶快樂。不過大家看到這篇文章的時(shí)候估計(jì)已經(jīng)過完國慶了 ????。
上一篇我們寫了如何通過 SelfContained 模式發(fā)布程序(不安裝運(yùn)行時(shí)運(yùn)行.NET程序)達(dá)到不需要在目標(biāo)機(jī)器上安裝 runtime 就可以運(yùn)行 .NET 程序的目標(biāo)。其實(shí)除了標(biāo)準(zhǔn)的 self-contained 微軟還給我們帶來了 Native AOT 發(fā)布模式。是的你沒看錯(cuò),通過該技術(shù)我們的 .NET 程序會(huì)直接編譯為 Native 代碼而不再是 IL ,程序運(yùn)行的時(shí)候直接就是機(jī)器碼,不再需要 JIT 編譯。通過 AOT 技術(shù),我們的程序啟動(dòng)會(huì)變的非??觳⑶沂褂酶俚膬?nèi)存,并且運(yùn)行的時(shí)候不需要在機(jī)器上安裝任何運(yùn)行時(shí)。
前階段 .NET7 發(fā)布了第一個(gè) RC 版本,標(biāo)志著正式版的 AOT 馬上會(huì)隨 .NET7 發(fā)布而到來。所以趁著國慶趕緊體驗(yàn)一把。
創(chuàng)新互聯(lián)公司主要從事做網(wǎng)站、成都做網(wǎng)站、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)渾江,十多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):13518219792
現(xiàn)階段 .NET7 還在RC,所以我們選擇安裝 SDK 7.0.100-rc.1.22431.12 ,操作系統(tǒng)是 WIN10 64位,開發(fā)工具是 VS2022 17.4.0 Preview 2.1 。正式版的 VS2022 是沒辦法選擇目標(biāo)框架 .NET7 的,但是其實(shí)可以手動(dòng)改 csproj 文件,所以 VS2022 Preview 不是必須的。
我們新建一個(gè)控制臺(tái)程序,目標(biāo)框架選擇 NET7
(如果使用正式版的 VS2022 沒有辦法選擇 net7 ,可以直接編輯 csproj 文件),右鍵項(xiàng)目選擇“編輯項(xiàng)目文件”,在 PropertyGroup
節(jié)點(diǎn)下添加 PublishAot
:
Exe
net7.0
enable
enable
true
修改 main 方法:
Console.WriteLine("Hello, AOT!");
Console.Read();
使用 dotnet
命令進(jìn)行發(fā)布:
dotnet publish -r win-x64 -c Release
AOT 發(fā)布相比正常發(fā)布會(huì)慢一點(diǎn),等待發(fā)布成功后,我們可以到以下目錄查看 bin\Release\net7.0\win-x64\publish
:
我們可以看到生成的 exe 文件只有 3.48MB ,相比普通單文件發(fā)布加裁剪過后的程序小了不少。
我們把這個(gè) exe 程序復(fù)制到一臺(tái)沒有安裝 .net 環(huán)境的服務(wù)器上,順利運(yùn)行起來了。
上面我們測(cè)試了一下控制臺(tái)程序的 AOT 發(fā)布,相對(duì)比較簡單沒有什么問題。下面讓我們?cè)囋噾?yīng)用范圍最為廣泛的 ASP.NET CORE 項(xiàng)目 AOT 發(fā)布行不行。
新建一個(gè) ASP.NET CORE WebApi 項(xiàng)目,目標(biāo)框架選擇 NET7 。同樣的操作編輯 csproj 文件,添加 PublishAot 屬性:
net7.0
enable
enable
true
同樣使用 dotnet cli 命令進(jìn)行發(fā)布:
dotnet publish -r win-x64 -c Release
不同于上面控制臺(tái)項(xiàng)目的發(fā)布,ASP.NET CORE 項(xiàng)目的 AOT 發(fā)布會(huì)出現(xiàn)很多警告信息,暫且忽略。
等到發(fā)布完成后,我們看到生成了一個(gè) 27MB 大小的 exe 文件。雙擊運(yùn)行起來,不得不提一句,這個(gè)啟動(dòng)速度真的是肉眼可見的快,雙擊之后瞬間就啟動(dòng)了。這個(gè)就是 AOT 發(fā)布最大的優(yōu)勢(shì)了。
訪問一下默認(rèn)生成的那個(gè) Action 方法:http://localhost:5000/WeatherForecast/ 成功的輸出了天氣信息。
以上通過簡單的測(cè)試,ASP.NET CORE WebApi 項(xiàng)目順利的跑起來了, 當(dāng)然他只是一個(gè)簡單的示例項(xiàng)目,我們生產(chǎn)的項(xiàng)目相比這些要復(fù)雜多了。經(jīng)過更深入的測(cè)試,發(fā)現(xiàn)現(xiàn)階段 ASP.NET CORE 進(jìn)行 AOT 發(fā)布后有一個(gè)比較麻煩的問題,那就是 JSON 序列化。
以下代碼是默認(rèn)生成的 WeatherForecastController 的 GET 方法,這個(gè)方法是個(gè)標(biāo)準(zhǔn)的同步方法,進(jìn)行 AOT 發(fā)布后序列化沒有任何問題。
[HttpGet]
public WeatherForecast[] Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
但是如果把代碼改成異步,或者說的更直白一點(diǎn)的話,返回值是 Task<T
> 類型就會(huì)出現(xiàn)問題。比如把上面的代碼使用 Task.FromResult 改造一下,使返回值變成 Task
[HttpGet]
public async Task Get()
{
var arr = Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
var result = await Task.FromResult(arr);
return result;
}
改造的程序進(jìn)行 AOT 發(fā)布后運(yùn)行,訪問對(duì)應(yīng)的接口程序不會(huì)有任何報(bào)錯(cuò),但是返回值是個(gè)空對(duì)象的json:
{}
嘗試修復(fù)該問題,并沒有特別的好辦法,目前能夠勉強(qiáng)使用的辦法是使用 System.Text.Json source generator 模式進(jìn)行序列化:
首先編寫一個(gè) WeatherForecastContext 類繼承 JsonSerializerContext,并且標(biāo)記為 partial。為啥要標(biāo)記為 partial ?因?yàn)轭惖牧硗獠糠质?source generator 自動(dòng)生成的。
[JsonSerializable(typeof(Task))]
internal partial class WeatherForecastContext : JsonSerializerContext
{
}
第二步,在配置 services 的時(shí)候順便把 WeatherForecastContext 配置進(jìn)去。
builder.Services.AddControllers()
.AddJsonOptions(options => options.JsonSerializerOptions.AddContext());
通過以上操作,再次 AOT 發(fā)布后運(yùn)行程序,訪問接口,數(shù)據(jù)是能正確的返回了。但是有一點(diǎn)小瑕疵是Task對(duì)象自身的屬性也被序列化出來了。
{
"result": [
{
"date": "2022-10-08T19:14:26.1801524+08:00",
"temperatureC": 6,
"temperatureF": 42,
"summary": "Warm"
},
{
"date": "2022-10-09T19:14:26.1816645+08:00",
"temperatureC": -9,
"temperatureF": 16,
"summary": "Bracing"
},
{
"date": "2022-10-10T19:14:26.1816648+08:00",
"temperatureC": -1,
"temperatureF": 31,
"summary": "Sweltering"
},
{
"date": "2022-10-11T19:14:26.181665+08:00",
"temperatureC": -17,
"temperatureF": 2,
"summary": "Balmy"
},
{
"date": "2022-10-12T19:14:26.1816651+08:00",
"temperatureC": -16,
"temperatureF": 4,
"summary": "Freezing"
}
],
"asyncState": null,
"creationOptions": 0,
"exception": null,
"id": 1,
"isCanceled": false,
"isCompleted": true,
"isCompletedSuccessfully": true,
"isFaulted": false,
"status": 5
}
以上對(duì)控制臺(tái)程序,web 程序進(jìn)行了測(cè)試,接下來順便對(duì)桌面 GUI 程序測(cè)試一下吧。
很遺憾,不管是 WINFROM 還是 WPF 程序,進(jìn)行 AOT 發(fā)布的時(shí)候直接都會(huì)報(bào)錯(cuò),提示不支持。
AOT 發(fā)布的程序會(huì)有一些限制,我們編寫的時(shí)候需要注意:
以上是直接復(fù)制的英文文檔(原文地址在文末),因?yàn)橛⑽牟皇呛芎茫贿M(jìn)行翻譯了,怕誤導(dǎo)大家。主要需要注意的就是 1,2 兩點(diǎn) ,關(guān)于動(dòng)態(tài)加載類庫跟動(dòng)態(tài)生成代碼的問題。我想序列化的問題大概也就是出在這里,因?yàn)閭鹘y(tǒng)的序列化需要大量的使用動(dòng)態(tài)生成代碼技術(shù)。
通過以上我們對(duì) .NET 上最常用的幾種程序進(jìn)行了 Native AOT 發(fā)布的測(cè)試??傮w來說控制臺(tái)跟ASP.NET CORE 項(xiàng)目能用,WINFROM 跟 WPF 不能用。比較遺憾的有兩個(gè)點(diǎn):
另外來說說性能,有同學(xué)可能覺得 Native AOT 之后性能會(huì)有很大的提升,畢竟大家都迷信 Native 速度快嘛。但是經(jīng)過大佬們的測(cè)試事實(shí)上 AOT 之后跟沒有 AOT 的代碼性能基本在伯仲之間,有些地方甚至不如非 Native 的代碼。為什么?因?yàn)榉?Native 代碼可以進(jìn)行運(yùn)行時(shí) JIT 啊,可以在運(yùn)行時(shí)分析代碼對(duì)熱點(diǎn)代碼進(jìn)行二次 JIT 來提升性能,而 Native AOT 之后的代碼做不到這點(diǎn)。
Native AOT Deployment
Try the new System.Text.Json source generator
AOT和單文件發(fā)布對(duì)程序性能的影響