AOT使用經驗總結

一、引言

站長接觸 AOT 已有 3 個月之久,此前在《好消息:NET 9 X86 AOT的突破 - 支持老舊Win7與XP環(huán)境》一文中就有所提及。在這段時間里,站長使用 Avalonia 開發(fā)的項目也成功完成了 AOT 發(fā)布測試。然而,這一過程并非一帆風順。站長在項目功能完成大半部分才開始進行 AOT 測試,期間遭遇了不少問題,可謂是 “踩坑無數”。為了方便日后回顧,也為了給廣大讀者提供參考,在此將這段經歷進行總結。

.NET AOT是將.NET代碼提前編譯為本機代碼的技術。其優(yōu)勢眾多,啟動速度快,減少運行時資源占用,還提高安全性。AOT發(fā)布后無需再安裝.NET運行時等依賴。.NET 8、9 AOT發(fā)布后,可在XP、Win7非SP1操作系統(tǒng)下運行。這使得應用部署更便捷,能適應更多老舊系統(tǒng)環(huán)境,為開發(fā)者拓展了應用場景,在性能提升的同時,也增加了系統(tǒng)兼容性,讓.NET應用的開發(fā)和部署更具靈活性和廣泛性,給用戶帶來更好的體驗。

二、經驗之談

(一)測試策略的重要性

從項目創(chuàng)建伊始,就應養(yǎng)成良好的習慣,即只要添加了新功能或使用了較新的語法,就及時進行 AOT 發(fā)布測試。否則,問題積累到后期,解決起來會異常艱難,站長就因前期忽視了這一點,付出了慘痛的代價。無奈的解決方法是重新創(chuàng)建項目,然后逐個還原功能并進行 AOT 測試。經過了一周的加班AOT測試,每個 AOT 發(fā)布過程大致如下:

  1. 內網 AOT 發(fā)布一次需 2、3 分鐘,這段時間只能看看需求文檔、技術文章、需求文檔、技術文章。。。
  2. 發(fā)布完成,運行無效果,體現(xiàn)在雙擊未出現(xiàn)界面,進程列表沒有它,說明程序崩潰了,查看系統(tǒng)應用事件日志,日志中通常會包含異常警告信息。
  3. 依據日志信息檢查代碼,修改相關 API。
  4. 再次進行 AOT 發(fā)布,重復上述 1 - 3 步驟。

經過一周的努力,項目 AOT 后功能測試終于正常,至此收工。

(二)AOT 需要注意的點及解決方法

1. 添加rd.xml

在主工程創(chuàng)建一個XML文件,例如Roots.xml,內容大致如下:

<linker>
    <assembly fullname="CodeWF.Toolbox.Desktop" preserve="All" />
</linker>

需要支持AOT的工程,在該XML中添加一個assembly節(jié)點,fullname是程序集名稱,CodeWF.Toolbox.Desktop是站長小工具的主工程名,點擊查看源碼。

在主工程添加ItemGroup節(jié)點關聯(lián)該XML文件:

<ItemGroup>
    <TrimmerRootDescriptor Include="Roots.xml" />
</ItemGroup>

2. Prism支持

站長使用了Prism框架及DryIOC容器,若要支持 AOT,需要添加以下 NuGet 包:

<PackageReference Include="Prism.Avalonia" Version="8.1.97.11073" />
<PackageReference Include="Prism.DryIoc.Avalonia" Version="8.1.97.11073" />

rd.xml需要添加

<assembly fullname="Prism" preserve="All" />
<assembly fullname="DryIoc" preserve="All" />
<assembly fullname="Prism.Avalonia" preserve="All" />
<assembly fullname="Prism.DryIoc.Avalonia" preserve="All" />

3. App.config讀寫

在.NET Core中使用System.Configuration.ConfigurationManager包操作App.config文件,rd.xml需添加如下內容:

<assembly fullname="System.Configuration.ConfigurationManager" preserve="All" />

使用Assembly.GetEntryAssembly().location失敗,目前使用ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None)獲取的應用程序程序配置,指定路徑的方式后續(xù)再研究。

4. HttpClient使用

rd.xml添加如下內容:

<assembly fullname="System.Net.Http" preserve="All" />

5. Dapper支持

Dapper的AOT支持需要安裝Dapper.AOT包,rd.xml添加如下內容:

<assembly fullname="Dapper" preserve="All" />
<assembly fullname="Dapper.AOT" preserve="All" />

數據庫操作的方法需要添加DapperAOT特性,舉例如下:

[DapperAot]
public static bool EnsureTableIsCreated()
{
    try
    {
        using var connection = new SqliteConnection(DBConst.DBConnectionString);
        connection.Open();

        const string sql = $@"
            CREATE TABLE IF NOT EXISTS {nameof(JsonPrettifyEntity)}(
                {nameof(JsonPrettifyEntity.IsSortKey)} Bool,
                {nameof(JsonPrettifyEntity.IndentSize)} INTEGER
        )";

        using var command = new SqliteCommand(sql, connection);
        return command.ExecuteNonQuery() > 0;
    }
    catch (Exception ex)
    {
        return false;
    }
}

6. System.Text.Json

參考JsonExtensions.cs

序列化

public static bool ToJson<T>(this T obj, out string? json, out string? errorMsg)
{
    if (obj == null)
    {
        json = default;
        errorMsg = "Please provide object";
        return false;
    }

    var options = new JsonSerializerOptions()
    {
        WriteIndented = true,
        Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
        TypeInfoResolver = new DefaultJsonTypeInfoResolver()
    };
    try
    {
        json = JsonSerializer.Serialize(obj, options);
        errorMsg = default;
        return true;
    }
    catch (Exception ex)
    {
        json = default;
        errorMsg = ex.Message;
        return false;
    }
}

反序列化

public static bool FromJson<T>(this string? json, out T? obj, out string? errorMsg)
{
    if (string.IsNullOrWhiteSpace(json))
    {
        obj = default;
        errorMsg = "Please provide json string";
        return false;
    }

    try
    {
        var options = new JsonSerializerOptions()
        {
            Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
            TypeInfoResolver = new DefaultJsonTypeInfoResolver()
        };
        obj = JsonSerializer.Deserialize<T>(json!, options);
        errorMsg = default;
        return true;
    }
    catch (Exception ex)
    {
        obj = default;
        errorMsg = ex.Message;
        return false;
    }
}

7. 反射問題

參考項目CodeWF.NetWeaver

  1. 創(chuàng)建指定類型的List<T>Dictionary<T>實例:
public static object CreateInstance(Type type)
{
    var itemTypes = type.GetGenericArguments();
    if (typeof(IList).IsAssignableFrom(type))
    {
        var lstType = typeof(List<>);
        var genericType = lstType.MakeGenericType(itemTypes.First());
        return Activator.CreateInstance(genericType)!;
    }
    else
    {
        var dictType = typeof(Dictionary<,>);
        var genericType = dictType.MakeGenericType(itemTypes.First(), itemTypes[1]);
        return Activator.CreateInstance(genericType)!;
    }
}
  1. 反射調用List<T>Dictionary<T>Add方法添加元素失敗,下面是偽代碼:
// List<T>
var addMethod = type.GetMethod("Add");
addMethod.Invoke(obj, new[]{ child })
    
// Dictionary<Key, Value>
var addMethod = type.GetMethod("Add");
addMethod.Invoke(obj, new[]{ key, value })

解決辦法,轉換為實現(xiàn)的接口調用:

// List<T>
(obj as IList).Add(child);

// Dictionary<Key, Value>
(obj as IDictionary)[key] = value;
  1. 獲取數組、List<T>Dictionary<key, value>的元素個數

同上面Add方法反射獲取Length或Count屬性皆返回0,value.Property("Length", 0),封裝的Property非AOT運行正確:

public static T Property<T>(this object obj, string propertyName, T defaultValue = default)
{
    if (obj == null) throw new ArgumentNullException(nameof(obj));
    if (string.IsNullOrEmpty(propertyName)) throw new ArgumentNullException(nameof(propertyName));

    var propertyInfo = obj.GetType().GetProperty(propertyName);
    if (propertyInfo == null)
    {
        return defaultValue;
    }

    var value = propertyInfo.GetValue(obj);

    try
    {
        return (T)Convert.ChangeType(value, typeof(T));
    }
    catch (InvalidCastException)
    {
        return defaultValue;
    }
}

AOT成功:直接通過轉換為基類型或實現(xiàn)的接口調用屬性即可:

// 數組
var length = ((Array)value).Length;

// List<T>
 if (value is IList list)
{
    var count = list.Count;
}

// Dictionary<key, value>
if (value is IDictionary dictionary)
{
    var count = dictionary.Count;
}

8. Windows 7支持

如遇AOT后無法在Windows 7運行,請?zhí)砑?code>YY-Thunks包:

<PackageReference Include="YY-Thunks" Version="1.1.4-Beta3" />

并指定目標框架為net9.0-windows

9. Winform\兼容XP

如果第8條后還運行不了,請參考上一篇文章《.NET 9 AOT的突破 - 支持老舊Win7與XP環(huán)境 - 碼界工坊 (dotnet9.com)》添加VC-LTL包,這里不贅述。

10. 其他

還有許多其他需要注意的地方,后續(xù)想起來逐漸完善本文。

三、總結

AOT 發(fā)布測試雖然過程中可能會遇到諸多問題,但通過及時的測試和正確的配置調整,最終能夠實現(xiàn)項目的順利發(fā)布。希望以上總結的經驗能對大家在 AOT 使用過程中有所幫助,讓大家在開發(fā)過程中少走彎路,提高項目的開發(fā)效率和質量。同時,也期待大家在實踐中不斷探索和總結,共同推動技術的進步和發(fā)展。

AOT可參考項目:

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,004評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內容