主頁 > .NET開發 > 通過 .NET NativeAOT 實作用戶體驗升級

通過 .NET NativeAOT 實作用戶體驗升級

2021-03-02 06:05:14 .NET開發

前言

TypedocConverter 是我先前因幫助維護 monaco-editor-uwp 但苦于 monaco editor 的 API 實在太多,手寫 C# 的型別系結十分不劃算而發起的一個專案,

這個工具可以將 typedoc 根據 TypeScript 生成的 JSON 檔案直接生成對應的 C# 型別系結代碼,并提供完整的 JSON 序列化支持,因此使用這個工具可以大大降低移植 TypeScript 庫到 .NET 上的困難,(至于為什么是從 typedoc 而不是從 TypeScript 直接 parse,其實只是因為太懶了不想寫 TypeScript 的 parser)

TypedocConverter 使用 F# 撰寫,雖然使用 .NET 5 可以做到程式集裁剪后使用單檔案自托管發布,但是我一直在想如果能使用 AOT 技術將整個程式編譯為 native binary 那就好了,這樣的話用戶在使用的時候將不需要運行 .NET 的運行時,也不需要 JIT,而是直接運行機器代碼,

工具除了功能性之外,最重要的就是用戶體驗,這樣做將大大提升程式的啟動速度(雖然原本已經夠快了,但是我想將 100ms 的啟動時間縮短到不到 1ms),使得用戶使用該工具時不需要任何的等待,

AOT 方案調研

.NET 一直以來都有一個叫做 CoreRT 的專案,使用該工具可以將 .NET 程式集編譯到 native binary,然而這個專案自從 2018 年官方就沒有再積極維護,但是由于社區的強烈呼聲以及某個微軟的合作伙伴的專案需要 AOT 技術,并表示如果沒有這項技術將不再使用 .NET,于是這個專案原地復活,以 NativeAOT 的名字轉移到了 runtimelab 并作為 .NET 6 的 P0(最高) 優先級實驗性作業項(即提供帶支持的官方 preview,而不再是原來的萬年 alpha),目前支持 win-x64linux-x64osx-x64,對于 ARM64 、移動平臺和瀏覽器(WebAssembly)的支持在計劃當中,

借著這個貧訓,我決定使用該方案將專案編譯為原生鏡像,

NativeAOT 原理

.NET 的 NativeAOT 的思路其實很簡單:

  • 首先需要一個 AOT 友好的、用于 NativeAOT 的核心庫 (System.Private.CoreLib)實作,提供型別和實作查找、型別決議等方法
  • 掃描程式集,記錄用到的型別和方法
  • 呼叫 RyuJIT 介面,生成型別的元資料,為所有的方法生成代碼,最終產生出 obj 二進制檔案
  • 呼叫聯結器(MSVC 或 clang),將產生的 obj 與 GC 和系統庫等鏈接成為最終的可執行檔案

現階段 NativeAOT 基本已經完成,剩余的部分作業則是一些修補和完善,以及對新版本 .NET 的跟進(目前還沒有跟進 C# 8 之后牽扯到運行時修改的特性,如默認介面方法實作和模塊初始化器等等),

可能你會問這和 .NET Native 技術有何不同?不同之處在于 .NET Native 使用 UTC 編譯器(MSVC 后端)進行代碼生成,而 NativeAOT 使用 RyuJIT 進行代碼生成,

關于 .NET NativeAOT 完整的使用檔案可以參考:using-native-aot,

針對 NativeAOT 改造專案

NativeAOT 使用非常簡單,只需要修改 csproj 專案檔案即可:

<PropertyGroup>
  <IlcOptimizationPreference>Speed</IlcOptimizationPreference>
  <IlcFoldIdenticalMethodBodies>true</IlcFoldIdenticalMethodBodies>
</PropertyGroup>
<ItemGroup>
  <PackageReference Include="Microsoft.DotNet.ILCompiler" Version="6.0.0-*" />
</ItemGroup>

IlcOptimizationPreference 指定 Speed 表示以最大性能為目標生成代碼(如果指定 Size 則表示以最小程式為目標生成代碼),

IlcFoldIdenticalMethodBodies 引數則可以將相同的方法體合并,有助于減小體積,

最后則是新的 Microsoft.DotNet.ILCompiler,這是 NativeAOT 編譯器本體,通過 wildcard 指定 6.0.0-* 版本,這樣每次編譯都會獲取最新的版本,

由于 Microsoft.DotNet.ILCompiler 來自實驗倉庫的 artifacts,而沒有發布在官方的 nuget 源,需要新建 nuget.config 額外將實驗倉庫的 artifacts 作為源引入:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <packageSources>
    <add key="dotnet-experimental" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-experimental/nuget/v3/index.json" />
  </packageSources>
</configuration>

如此一來便大功告成了,這就開始編譯:

dotnet publish -c Release -r win-x64

伴隨而來的是大量的警告:

AOT analysis warning IL9700: Microsoft.FSharp.Reflection.FSharpType.MakeFunctionType(Type,Type): Calling 'System.Type.MakeGenericType(Type[])' which has `RequiresDynamicCodeAttribute` can break functionality when compiled fully ahead of time. The native code for this instantiation might not be available at runtime.
AOT analysis warning IL9700: Microsoft.FSharp.Reflection.FSharpValue.MakeFunction(Type,FSharpFunc`2<Object,Object>): Calling 'System.Type.MakeGenericType(Type[])' which has `RequiresDynamicCodeAttribute` can break functionality when compiled fully ahead of time. The native code for this instantiation might not be available at runtime.
...

觀察警告可以發現,這是分析器報出來的,理由很簡單:NativeAOT 是不支持運行時動態代碼生成的,但是 MakeGenericType 在需要在運行時產生型別,因此可能不受支持,

為什么說是可能呢?因為 NativeAOT 條件下,不支持運行時產生新的型別,但是對于已經生成代碼的型別則是完全支持的,

由于專案沒有用到 System.Reflection.Emit 在運行時動態織入 IL,也沒有用到 Assembly.LoadFile 等動態加載程式集,更沒有用到 C++/CLI 和 COM,因此是 NativeAOT 兼容的,

編譯速度尚可,只等待了半分鐘,編譯完成后產生了一個 29mb 的 exe,體積還不夠優秀,但是先運行看看:

> ./TypedocConverter
[Error] No input file
Typedoc Converter Arguments:
--inputfile [file]: input file
--namespace [namespace]: specify namespace for generated code
--splitfiles [true|false]: whether to split code to different files
--outputdir [path]: used for place code files when splitfiles is true
--outputfile [path]: used for place code file when splitfiles is false
--number-type [int/decimal/double...]: config for number type mapping
--promise-type [CLR/WinRT]: config for promise type mapping, CLR for Task and WinRT for IAsyncAction/IAsyncOperation
--any-type [object/dynamic...]: config for any type mapping
--array-type [Array/IEnumerable/List...]: config for array type mapping
--nrt-disabled [true|false]: whether to disable Nullable Reference Types
--use-system-json [true|false]: whether to use System.Text.Json instead of Newtonsoft.Json

一瞬間就運行了起來,完全感受不到啟動時間(體感小于 1ms),這個體驗太爽了,

可是正當我高興的時候,使用一個實際的 JSON 檔案對功能進行測驗,卻報錯了:

Unhandled Exception: EETypeRva:0x013EC198(System.Reflection.MissingRuntimeArtifactException): MakeGenericMethod() cannot create this generic method instantiation because no code was generated for it: 'Microsoft.FSharp.Collections.ListModule.OfSeq<System.Int32>(System.Collections.Generic.IEnumerable<System.Int32>)'.
   at Internal.Reflection.Core.Execution.ExecutionEnvironment.GetMethodInvoker(RuntimeTypeInfo, QMethodDefinition, RuntimeTypeInfo[], MemberInfo) + 0x144
   at System.Reflection.Runtime.MethodInfos.NativeFormat.NativeFormatMethodCommon.GetUncachedMethodInvoker(RuntimeTypeInfo[], MemberInfo) + 0x50
   at System.Reflection.Runtime.MethodInfos.RuntimeMethodInfo.get_MethodInvoker() + 0xa1
   at System.Reflection.Runtime.MethodInfos.RuntimeNamedMethodInfo`1.MakeGenericMethod(Type[]) + 0x104
   ...

可以看到方法 Microsoft.FSharp.Collections.ListModule.OfSeq<System.Int32>(System.Collections.Generic.IEnumerable<System.Int32> 缺失了,

這是因為 NativeAOT 編譯器并沒有通過代碼路徑分析出該型別,因此沒有為該型別生成代碼,導致運行時嘗試創建該型別時由于找不到實作代碼而出錯,

因此,需要通過 Runtime Directives 指示編譯器生成指定型別和方法的代碼,方法是創建一個 rd.xml 并引入專案:

 <ItemGroup>
  <RdXmlFile Include="rd.xml" />
 </ItemGroup>

然后在 rd.xml 中撰寫需要編譯器額外生成的型別和方法,經過一番試錯之后,我寫出了如下的代碼:

<Directives>
  <Application>
    <Assembly Name="FSharp.Core" Dynamic="Required All">
      <Type Name="Microsoft.FSharp.Collections.ListModule" Dynamic="Required All">
        <Method Name="OfSeq" Dynamic="Required">
          <GenericArgument Name="System.Int32,System.Private.CoreLib" />
        </Method>
      </Type>
      <Type Name="Microsoft.FSharp.Core.PrintfImpl+Specializations`3[[System.Object,System.Private.CoreLib],[System.Object,System.Private.CoreLib],[System.Object,System.Private.CoreLib]]" Dynamic="Required All">
        <Method Name="CaptureFinal1" Dynamic="Required">
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
        </Method>
        <Method Name="CaptureFinal2" Dynamic="Required">
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
        </Method>
        <Method Name="CaptureFinal3" Dynamic="Required">
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
        </Method>
        <Method Name="OneStepWithArg" Dynamic="Required">
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
        </Method>
        <Method Name="Capture1" Dynamic="Required">
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
        </Method>
        <Method Name="Capture2" Dynamic="Required">
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
        </Method>
        <Method Name="Capture3" Dynamic="Required">
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
          <GenericArgument Name="System.Object,System.Private.CoreLib" />
        </Method>
      </Type> 
    </Assembly>

    <Assembly Name="System.Linq" Dynamic="Required All">
      <Type Name="System.Linq.Enumerable" Dynamic="Required All">
        <Method Name="ToArray" Dynamic="Required">
          <GenericArgument Name="System.Int32,System.Private.CoreLib" />
        </Method>
      </Type>
    </Assembly>

  </Application>
</Directives>

稍微對上面的東西進行一下解釋:Name 用于指定型別,, 前后分別是型別的完整名稱和型別來自的程式集名稱,.NET 中的各種基礎型別都來源于 System.Private.CoreLibmscorlib,詳細的格式說明可以參考 rd-xml-format,

在 .NET 中,編譯器會為所有的值型別的泛型引數特化一份實作,而所有的參考型別引數共享一份實作,這么做其實原因顯而易見,因為參考型別背后只是一個指標罷了,因此根據這個特點,所有的參考型別都無需指定實際的型別引數,統一指定一個 System.Object 就好了;而對于值型別作為型別引數則需要指出生成什么型別的代碼,

經過上面一番折騰之后,重新編譯運行,這次所有的功能均正常了,啟動速度飛快,運行時性能也非常棒,并且純靜態鏈接無需安裝任何運行時就能運行,體驗幾乎和 C++ 撰寫出來的程式一樣,

程式體積優化

上面一系列操作之后,雖然啟動和運行速度很快,但是生成的程式大小有 30 mb,還是有些大,那么接下來在不犧牲運行時代碼性能的情況下,針對程式體積進行優化,

首先指定 TrimMode為 Link,這可以使 NativeAOT 采用更加激進的程式集剪裁方案,將代碼路徑中沒有被參考的代碼以方法為粒度刪掉;另外,想到自己的程式不需要國際化支持,因此可以洗掉掉沒有用的多語言支持及其資源檔案,

<PropertyGroup>
  <TrimMode>Link</TrimMode>
  <InvariantGlobalization>true</InvariantGlobalization>
</PropertyGroup>

重新進行編譯,這個時候產生的 exe 大小只有 27mb 了,運行測驗:

Unhandled Exception: Newtonsoft.Json.JsonSerializationException: Unable to find a constructor to use for type Definitions+Reflection. A class should either have a default constructor, one constructor with arguments or a constructor marked with the JsonConstructor attribute. Path 'id', line 2, position 6.
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateNewObject(JsonReader, JsonObjectContract, JsonProperty, JsonProperty, String, Boolean&) + 0x1d1
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateObject(JsonReader, Type, JsonContract, JsonProperty, JsonContainerContract, JsonProperty, Object) + 0x2cc
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.CreateValueInternal(JsonReader, Type, JsonContract, JsonProperty, JsonContainerContract, JsonProperty, Object) + 0xa4
   at Newtonsoft.Json.Serialization.JsonSerializerInternalReader.Deserialize(JsonReader, Type, Boolean) + 0x26e
   at Newtonsoft.Json.JsonSerializer.DeserializeInternal(JsonReader, Type) + 0xf8
   at Newtonsoft.Json.JsonConvert.DeserializeObject(String, Type, JsonSerializerSettings) + 0x93
   at Newtonsoft.Json.JsonConvert.DeserializeObject[T](String, JsonSerializerSettings) + 0x2b
   at Program.main$cont@84(JsonSerializerSettings, Definitions.Config, Unit) + 0x31
   at TypedocConverter!<BaseAddress>+0x83a0ca

根據報錯資訊我們知道是 JSON 反序列化程序出了問題,問題在于 Definitions+Reflection 型別被裁剪掉了,由于我知道我自己的程式內進行 JSON 反序列化的目標型別都是來自于我自己的程式集本身,因此不必使用 rd.xml 那么麻煩,只需要告訴編譯器不要裁剪我自己的程式集中的型別即可(這對于泛型類實體無效,因為泛型型別實作是需要特化的):

<ItemGroup>
  <TrimmerRootAssembly Include="TypedocConverter" />
</ItemGroup>

接下來重新編譯運行,這次沒問題了,

最終程式的大小是 27mb,相比 30mb 并沒有小太多,不過這也正常,畢竟前面寫的 rd.xml 中,由于偷懶,通過 Dynamic="Require All" 保留了 F# 核心庫中的所有型別,如果我去掉 Dynamic="Require All" 的話,最終編譯出 22mb 的二進制檔案,但是需要更多的精力調研有哪些型別需要寫進 rd.xml

通過 zip 壓縮之后只剩下 11mb,這個體積我覺得已經不錯了,

當然,要注意的是,Windows 下除錯符號檔案默認作為單獨的 pdb 檔案提供,而在 *inx 下除錯符號是直接內嵌到程式二進制資料中的,因此在非 Windows 平臺下需要使用 strip 命令將符號裁剪掉,否則你將得到一個非常大的二進制程式檔案,

strip ./TypedocConverter

想要看看最終效果的可以去此處下載含 Native 名稱的 Release 檔案體驗:https://github.com/hez2010/TypedocConverter/releases,

到這里有人可能好奇 NativeAOT 最小能做到多小?經過實驗,禁用反射并取消 root 所有程式集后的 hello world 專案可以做到不到 1mb 的體積,

已知問題和限制

.NET NativeAOT 預計會在 .NET 6 將會為嘗鮮者提供帶支持的預覽(其實已經足夠穩定),現階段有一些比較影響使用的已知問題,我將在這里列出,

由于缺少實作而不支持(主要是 C# 8 之后的需要運行時改變的特性),但是短期內會被解決的問題:

  • 不支持含泛型方法的默認介面方法實作
  • 不支持協變回傳
  • try-catch 陳述句中不支持 catch (T),即將泛型引數作為 catch 的例外型別
  • 不支持模塊初始化器

短期內不會被解決的問題:

  • 不支持 COM
  • 不支持 C++/CLI

受限于運行時無 JIT 而無法實作的:

  • 運行時動態生成代碼(如:System.Reflection.Emit
  • 運行時動態加載程式集(如:Assembly.LoadFile
  • 無限泛型遞回呼叫

有人可能不理解什么叫做無限泛型遞回呼叫,我通過代碼解釋一下,假如你撰寫了如下代碼:

public void Foo<T>()
{
    if (bar)
    {
        Foo<U<T>>();
    }
}

那么會導致編譯器 Stack Overflow,原因是因為代碼中將 U<T>> 型別代入了 T,如果是不改變泛型嵌套層數呼叫的話(比如將 U 帶入 T),只需要通過 rd.xml 指定一下用到的型別即可解決;但是對于前后嵌套層數不一致的情況,編譯器在編譯時并不知道你到底會展開多少層代碼(NativeAOT 編譯器需要在編譯時展開所有的泛型并為涉及到的所有的方法和型別生成代碼),于是會無限的生成用于 TU<T>U<U<T>>... 的代碼,最終導致無法完成編譯, 而為什么有 JIT 的情況下不存在問題呢?是因為可以根據 bar 這個條件在運行時按需產生型別和生成代碼,

我曾經為 ReactvieX 和 Entity Framework Core 修復過類似的問題,如果想要了解詳情的話可以參考:

  • Fix infinite recursive generics in CatchScheduler
  • Fix infinite recursive generics

GUI 解決方案

由于短期內不支持 COM 和 C++/CLI,意味著 WPF 目前無法經過 NativeAOT 編譯為本機程式,但是好在 WPF 的跨平臺(基于 Skia 自繪)實作版本 Avalonia 完全不需要 COM,也不包含我上述列出的已知問題,因此今天就已經能夠使用它開發跨平臺的 UI 程式,

由于 0.10.0 版本做了大量優化,并引入了編譯時系結,性能有極大的提升,并且所有影片都以 60fps 呈現,還自帶一套 Fluent Design 的主題庫,體驗非常舒適,我經過嘗試之后,將自己的可視化通用旅行商問題解算器應用使用 NativeAOT 編譯后得到了一個 40mb 大小的應用程式(無需運行時),可以瞬間啟動且運行時記憶體占用不到 20mb,什么才是小而美(戰術后仰),

SATSP

左側是一個包含接近 70 萬個節點的折線圖,可以 60 fps 的體驗(其實可以更高,但對于桌面 GUI 應用來說 60 fps 渲染是一個默認的設定)隨意滑動、縮放和跟蹤點,完全不帶一點卡頓(某 WebGL 實作的 echart 這時候早已經停止了思考),

Web 解決方案

自然,ASP.NET Core 是支持 NativeAOT 的(MVC 中的 View 暫時除外),而 Entity Framework Core 由于使用了含泛型的默認介面方法實作暫時不支持 NativeAOT,隨著 NativeAOT 編譯器和庫的更新會解決,

至于重度依賴運行時織入 IL 的 Dapper,可能永遠也不會支持 NativeAOT,畢竟熊掌和魚不可兼得,

當然,通過 Source Generator 將動態生成代碼轉為靜態生成代碼不失為一種解決方案,

不過對于 ASP.NET Core,有一點需要注意:該框架通過反射程式集加載 Controller,因此代碼路徑中沒有直接參考 Controller 型別的代碼,編譯時所有的 Controller 都會被剪裁掉導致訪問所有的 API 都是 404,這一個問題同樣也是通過撰寫 rd.xml 告知編譯器保留型別來解決,

我將自己的一個沒有使用 ORM,只是使用 Microsoft.Data.Sqlite 的用于人員管理的 Web 服務經過 NativeAOT 編譯,得到了一個 30mb 的程式,運行后瞬間就能提供服務,記憶體占用只需要 20mb,且首次請求只需要 0.7ms,體驗非常的棒,這意味著在云原生環境下,尤其是擴容時,新建節點中的應用可以在極短時間內(一秒都不到)啟動并投入使用,而不是都啟動不久了還在等健康檢查的回應,預熱是什么?不存在的!

總結和展望

毫無疑問,NativeAOT 將能極大的改善 .NET 程式的啟動速度和運行性能,并自帶反破解屬性,真正做到 C# 的撰寫效率,C++ 的運行效率,在 .NET 5 的今天這套工具鏈其實發展狀況已經較為成熟了,想用的話已經可以提前體驗,國外其實已經有使用這套工具鏈上線生產專案的例子了,

.NET NativeAOT 目前還在不斷探索各種可能性,其中一個我認為比較有趣的是:

在 NativeAOT 編譯中,先將 IL 借助 RyuJIT 編譯到 LLVM IR,這個程序會對代碼進行 IL 特有模式相關的優化;然后將 LLVM IR 編譯到原生二進制程式,這個程序將會通過 LLVM 進行進一步的優化,使得編譯后的體積更小、運行時性能更強,

先前的之前編譯到 LLVM IR 的實驗 LLILC 的問題在于直接 target 到 LLVM IR 導致 RyuJIT 針對 IL 特定模式的優化缺失,而新的實驗當中,RyuJIT 作為“中端”,做好針對 IL 特定模式的優化后再送到 LLVM,避免了該不足之處,

未來 .NET NativeAOT 技術同樣會被帶到移動平臺和瀏覽器(WebAssembly)上,對于這套技術以后的發展我也會長期關注和跟進,

最后,希望 .NET 平臺越來越好,

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/265003.html

標籤:.NET Core

上一篇:一日一技:微信開發-發送模板訊息

下一篇:Redis快速入門及應用

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • WebAPI簡介

    Web體系結構: 有三個核心:資源(resource),URL(統一資源識別符號)和表示 他們的關系是這樣的:一個資源由一個URL進行標識,HTTP客戶端使用URL定位資源,表示是從資源回傳資料,媒體型別是資源回傳的資料格式。 接下來我們說下HTTP. HTTP協議的系統是一種無狀態的方式,使用請求/ ......

    uj5u.com 2020-09-09 22:07:47 more
  • asp.net core 3.1 入口:Program.cs中的Main函式

    本文分析Program.cs 中Main()函式中代碼的運行順序分析asp.net core程式的啟動,重點不是剖析原始碼,而是理清程式開始時執行的順序。到呼叫了哪些實體,哪些法方。asp.net core 3.1 的程式入口在專案Program.cs檔案里,如下。ususing System; us ......

    uj5u.com 2020-09-09 22:07:49 more
  • asp.net網站作為websocket服務端的應用該如何寫

    最近被websocket的一個問題困擾了很久,有一個需求是在web網站中搭建websocket服務。客戶端通過網頁與服務器建立連接,然后服務器根據ip給客戶端網頁發送資訊。 其實,這個需求并不難,只是剛開始對websocket的內容不太了解。上網搜索了一下,有通過asp.net core 實作的、有 ......

    uj5u.com 2020-09-09 22:08:02 more
  • ASP.NET 開源匯入匯出庫Magicodes.IE Docker中使用

    Magicodes.IE在Docker中使用 更新歷史 2019.02.13 【Nuget】版本更新到2.0.2 【匯入】修復單列匯入的Bug,單元測驗“OneColumnImporter_Test”。問題見(https://github.com/dotnetcore/Magicodes.IE/is ......

    uj5u.com 2020-09-09 22:08:05 more
  • 在webform中使用ajax

    如果你用過Asp.net webform, 說明你也算是.NET 開發的老兵了。WEBform應該是2011 2013左右,當時還用visual studio 2005、 visual studio 2008。后來基本都用的是MVC。 如果是新開發的專案,估計沒人會用webform技術。但是有些舊版 ......

    uj5u.com 2020-09-09 22:08:50 more
  • iis添加asp.net網站,訪問提示:由于擴展配置問題而無法提供您請求的

    今天在iis服務器配置asp.net網站,遇到一個問題,記錄一下: 問題:由于擴展配置問題而無法提供您請求的頁面。如果該頁面是腳本,請添加處理程式。如果應下載檔案,請添加 MIME 映射。 WindowServer2012服務器,添加角色安裝完.netframework和iis之后,運行aspx頁面 ......

    uj5u.com 2020-09-09 22:10:00 more
  • WebAPI-處理架構

    帶著問題去思考,大家好! 問題1:HTTP請求和回傳相應的HTTP回應資訊之間發生了什么? 1:首先是最底層,托管層,位于WebAPI和底層HTTP堆疊之間 2:其次是 訊息處理程式管道層,這里比如日志和快取。OWIN的參考是將訊息處理程式管道的一些功能下移到堆疊下端的OWIN中間件了。 3:控制器處理 ......

    uj5u.com 2020-09-09 22:11:13 more
  • 微信門戶開發框架-使用指導說明書

    微信門戶應用管理系統,采用基于 MVC + Bootstrap + Ajax + Enterprise Library的技術路線,界面層采用Boostrap + Metronic組合的前端框架,資料訪問層支持Oracle、SQLServer、MySQL、PostgreSQL等資料庫。框架以MVC5,... ......

    uj5u.com 2020-09-09 22:15:18 more
  • WebAPI-HTTP編程模型

    帶著問題去思考,大家好!它是什么?它包含什么?它能干什么? 訊息 HTTP編程模型的核心就是訊息抽象,表示為:HttPRequestMessage,HttpResponseMessage.用于客戶端和服務端之間交換請求和回應訊息。 HttpMethod類包含了一組靜態屬性: private stat ......

    uj5u.com 2020-09-09 22:15:23 more
  • 部署WebApi隨筆

    一、跨域 NuGet參考Microsoft.AspNet.WebApi.Cors WebApiConfig.cs中配置: // Web API 配置和服務 config.EnableCors(new EnableCorsAttribute("*", "*", "*")); 二、清除默認回傳XML格式 ......

    uj5u.com 2020-09-09 22:15:48 more
最新发布
  • C#多執行緒學習(二) 如何操縱一個執行緒

    <a href="https://www.cnblogs.com/x-zhi/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2943582/20220801082530.png" alt="" /></...

    uj5u.com 2023-04-19 09:17:20 more
  • C#多執行緒學習(二) 如何操縱一個執行緒

    C#多執行緒學習(二) 如何操縱一個執行緒 執行緒學習第一篇:C#多執行緒學習(一) 多執行緒的相關概念 下面我們就動手來創建一個執行緒,使用Thread類創建執行緒時,只需提供執行緒入口即可。(執行緒入口使程式知道該讓這個執行緒干什么事) 在C#中,執行緒入口是通過ThreadStart代理(delegate)來提供的 ......

    uj5u.com 2023-04-19 09:16:49 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    <a href="https://www.cnblogs.com/huangxincheng/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/214741/20200614104537.png" alt="" /&g...

    uj5u.com 2023-04-18 08:39:04 more
  • 記一次 .NET某醫療器械清洗系統 卡死分析

    一:背景 1. 講故事 前段時間協助訓練營里的一位朋友分析了一個程式卡死的問題,回過頭來看這個案例比較經典,這篇稍微整理一下供后來者少踩坑吧。 二:WinDbg 分析 1. 為什么會卡死 因為是表單程式,理所當然就是看主執行緒此時正在做什么? 可以用 ~0s ; k 看一下便知。 0:000> k # ......

    uj5u.com 2023-04-18 08:33:10 more
  • SignalR, No Connection with that ID,IIS

    <a href="https://www.cnblogs.com/smartstar/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/u36196.jpg" alt="" /></a>...

    uj5u.com 2023-03-30 17:21:52 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:15:33 more
  • 一次對pool的誤用導致的.net頻繁gc的診斷分析

    <a href="https://www.cnblogs.com/dotnet-diagnostic/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/3115652/20230225090434.png" alt=""...

    uj5u.com 2023-03-28 10:13:31 more
  • C#遍歷指定檔案夾中所有檔案的3種方法

    <a href="https://www.cnblogs.com/xbhp/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/957602/20230310105611.png" alt="" /></a&...

    uj5u.com 2023-03-27 14:46:55 more
  • C#/VB.NET:如何將PDF轉為PDF/A

    <a href="https://www.cnblogs.com/Carina-baby/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/2859233/20220427162558.png" alt="" />...

    uj5u.com 2023-03-27 14:46:35 more
  • 武裝你的WEBAPI-OData聚合查詢

    <a href="https://www.cnblogs.com/podolski/" target="_blank"><img width="48" height="48" class="pfs" src="https://pic.cnblogs.com/face/616093/20140323000327.png" alt="" /><...

    uj5u.com 2023-03-27 14:46:16 more