...

宣布 .NET 6 — 迄今爲止最快的 .NET

2021-12-13


2021 年 11 月 8 日 (以下是譯文)


歡迎使用 .NET 6。今天的發(fā)布是 .NET 團隊和社區一年多來努力的結果。C# 10 和 F# 6 提供了語言改進(jìn),使您的代碼更簡單、更好(hǎo)。性能(néng)有了巨大的提升,我們已經(jīng)看到降低了 Microsoft 托管雲服務的成(chéng)本。.NET 6 是第一個原生支持 Apple Silicon (Arm64) 的版本,并且還(hái)針對(duì) Windows Arm64 進(jìn)行了改進(jìn)。我們構建了一個新的動态配置文件引導優化 (PGO) 系統,該系統可提供僅在運行時(shí)才可能(néng)進(jìn)行的深度優化。使用dotnet monitorOpenTelemetry改進(jìn)了雲診斷。WebAssembly支持更強大、更高效。添加了新的 API,用于HTTP/3處理 JSON數學(xué),直接操作内存。.NET 6 將(jiāng)得到三年支持。開(kāi)發(fā)人員已經(jīng)開(kāi)始將(jiāng)應用程序升級到 .NET 6,我們已經(jīng)在生産中聽到了很好(hǎo)的早期結果。.NET 6 已爲您的應用做好(hǎo)準備。

您可以下載适用于 Linux、macOS 和 Windows 的.NET 6

有關Web 方案的新增功能(néng),請參閱ASP.NET Core帖子。

Visual Studio 2022 也在今天發(fā)布。閱讀公告觀看發(fā)布活動以了解有關發(fā)布的更多信息。

PowerShell 7.2也在今天發(fā)布,基于 .NET 6。PowerShell 用戶可以訪問與 .NET 開(kāi)發(fā)人員相同的性能(néng)改進(jìn)和 API。

.NET Conf是一個爲期三天的免費虛拟開(kāi)發(fā)人員活動,旨在慶祝 .NET 的主要版本。它將(jiāng)于明天開(kāi)始,并于 11 月 9 日至 11 日舉行,屆時(shí)將(jiāng)有來自我們團隊、Microsoft 團隊和更廣泛社區的演講者參加 80 多場會(huì)議。收聽學(xué)習并與我們互動

查看新的對(duì)話帖子,就(jiù)最新的 .NET 功能(néng)進(jìn)行工程師對(duì)工程師的深入讨論。

.NET 6 亮點

.NET 6 是:

該版本包括大約一萬個 git 提交。即使這(zhè)篇文章很長(cháng),它也跳過(guò)了許多改進(jìn)。您必須下載并試用 .NET 6 才能(néng)看到所有新内容。

支持

.NET 6 是一個長(cháng)期支持 (LTS) 版本,將(jiāng)支持三年。它支持多種(zhǒng)操作系統,包括 macOS Apple Silicon 和 Windows Arm64。

紅帽與 .NET 團隊合作紅帽企業 Linux 上支持 .NET。在 RHEL 8 及更高版本上,.NET 6 將(jiāng)可用于 AMD 和 Intel (x64_64)、ARM (aarch64) 以及 IBM Z 和 LinuxONE (s390x) 架構。

請開(kāi)始將(jiāng)您的應用程序遷移到 .NET 6,尤其是 .NET 5 應用程序。我們從早期采用者那裡(lǐ)聽說(shuō),從 .NET Core 3.1 和 .NET 5 升級到 .NET 6 很簡單。

Visual Studio 2022Visual Studio 2022 for Mac支持 .NET 6 。Visual Studio 2019、Visual Studio for Mac 8 或 MSBuild 16 不支持它。如果要使用 .NET 6,則需要升級到Visual Studio 2022(現在也是 64 位)。Visual Studio CodeC# 擴展支持 .NET 6 。

Azure 應用服務:

注意:如果您的應用已在應用服務上運行 .NET 6 預覽版或 RC 構建,則一旦 .NET 6 運行時(shí)和 SDK 部署到您所在的區域,它將(jiāng)在第一次重新啓動時(shí)自動更新。如果您部署了自包含應用程序,則需要重新構建和重新部署。

統一擴展平台

.NET 6 爲浏覽器桌面(miàn)IoT移動應用程序提供了一個統一的平台。底層平台已更新,以滿足所有應用程序類型的需求,并使您可以輕松地在所有應用程序中重用代碼。新功能(néng)和改進(jìn)可同時(shí)用于所有應用程序,因此您在雲中或移動設備上運行的代碼具有相同的行爲方式并具有相同的優勢。





随著(zhe)每個版本的發(fā)布,.NET 開(kāi)發(fā)人員的影響範圍不斷擴大。機器學(xué)習WebAssembly是最近添加的兩(liǎng)個。例如,通過(guò)機器學(xué)習,您可以編寫應用程序來查找流數據中的異常情況。使用 WebAssembly,您可以在浏覽器中托管 .NET 應用程序,就(jiù)像 HTML 和 JavaScript 一樣,或者將(jiāng)它們與 HTML 和 JavaScript 混合使用

最令人興奮的新增功能(néng)之一是.NET 多平台應用程序 UI (.NET MAUI)。您現在可以在單個項目中編寫代碼,從而提供跨桌面(miàn)和移動操作系統的現代客戶端應用程序體驗。.NET MAUI 的發(fā)布時(shí)間將(jiāng)比 .NET 6 晚一點。我們在 .NET MAUI 上投入了大量時(shí)間和精力,很高興能(néng)夠發(fā)布它并看到 .NET MAUI 應用程序投入生産。

當然,.NET 應用程序也可以在Windows 桌面(miàn)上使用(使用Windows 窗體WPF)以及在雲中使用http://ASP.NETCore。它們是我們提供時(shí)間最長(cháng)的應用程序類型,并且仍然非常受歡迎,我們在 .NET 6 中對(duì)它們進(jìn)行了改進(jìn)。

面(miàn)向(xiàng) .NET 6

繼續以廣泛的平台爲主題,在所有這(zhè)些操作系統上編寫 .NET 代碼很容易。

以 .NET 6目标,您需要使用 .NET 6 目标框架,如下所示:

net6.0

該目标框架名字對(duì)象(TFM),您可以訪問所有的跨平台的API,.NET提供。如果您正在編寫控制台應用程序、http://ASP.NETCore 應用程序或可重用的跨平台庫,這(zhè)是最佳選擇。net6.0

如果您的目标是特定的操作系統(例如編寫Windows 窗體或 iOS 應用程序),那麼(me)還(hái)有另一組 TFM(每個都(dōu)針對(duì)一個不言而喻的操作系統)供您使用。它們使您可以訪問所有 API以及一系列特定于操作系統的 API 。net6.0

  • net6.0-android

  • net6.0-ios

  • net6.0-maccatalyst

  • net6.0-tvos

  • net6.0-windows

每個無版本的 TFM 都(dōu)相當于針對(duì) .NET 6 支持的最低操作系統版本。如果您想要特定或訪問更新的 API,可以指定操作系統版本。

的和TFMS支持(同.NET 5)。Android 和 Apple TFM 是 .NET 6 的新增功能(néng),目前處于預覽階段。稍後(hòu)的 .NET 6 更新將(jiāng)支持它們。net6.0net6.0-windows

操作系統特定的 TFM 之間沒(méi)有兼容性關系。例如,與. 如果您想共享代碼,您需要使用帶有語句的源代碼或帶有目标代碼的二進(jìn)制文件來實現。net6.0-iosnet6.0-tvos#ifnet6.0

表現

自從我們啓動 .NET Core 項目以來,該團隊就(jiù)一直非常關注性能(néng)。Stephen Toub在捕捉每個版本的 .NET 性能(néng)進(jìn)展方面(miàn)做得非常出色。如果您還(hái)沒(méi)有機會(huì),我建議您查看他在 .NET 6 中的性能(néng)改進(jìn)帖子。

在這(zhè)篇博文中,我收集了一些您想了解的重大性能(néng)改進(jìn),包括文件 IO、界面(miàn)轉換、PGO 和 System.Text.Json。

動态 PGO

動态輪廓引導優化 (PGO)可以顯著(zhe)提高穩态性能(néng)。例如,PGO 使 TechEmpower JSON“MVC”套件的每秒請求數提高了 26%(510K -> 640K)。

動态 PGO 建立在分層編譯之上,它使方法能(néng)夠首先非常快速地編譯(稱爲“第 0 層”)以提高啓動性能(néng),然後(hòu)在啓用大量優化的情況下随後(hòu)重新編譯(稱爲“第 1 層”)一旦這(zhè)種(zhǒng)方法被證明是有效的。該模型使方法能(néng)夠在第 0 層中進(jìn)行檢測,以允許對(duì)代碼的執行進(jìn)行各種(zhǒng)觀察。當這(zhè)些方法在第 1 層重新編譯時(shí),從第 0 層執行中收集的信息將(jiāng)用于更好(hǎo)地優化第 1 層代碼。這(zhè)就(jiù)是機制的本質。

動态 PGO 的啓動時(shí)間將(jiāng)比默認運行時(shí)稍慢,因爲在第 0 層方法中運行額外的代碼來觀察方法行爲。

要啓用動态 PGO,請在您的應用程序將(jiāng)運行的環境中進(jìn)行設置。您還(hái)必須确保啓用分層編譯(默認情況下)。動态 PGO 是可選的,因爲它是一種(zhǒng)新的、有影響力的技術。我們希望發(fā)布選擇性使用和相關反饋,以确保它經(jīng)過(guò)全面(miàn)壓力測試。我們對(duì)分層編譯做了同樣的事(shì)情。至少一個非常大的 Microsoft 服務支持動态 PGO,并且已經(jīng)在生産中使用它。我們鼓勵您嘗試一下。DOTNET_TieredPGO=1

您可以在 .NET 6中的性能(néng)博文中看到更多關于動态 PGO 優勢的信息,包括以下微基準測試,它測量特定 LINQ 枚舉器的成(chéng)本。

private IEnumerator _source = Enumerable.Range(0, long.MaxValue).GetEnumerator();

[Benchmark]
public void MoveNext() => _source.MoveNext();

這(zhè)是有和沒(méi)有動态 PGO 的結果。




這(zhè)是一個相當大的差異,但也增加了代碼大小,這(zhè)可能(néng)會(huì)讓一些讀者感到驚訝。這(zhè)是 JIT 生成(chéng)的彙編代碼的大小,而不是内存分配(這(zhè)是一個更常見的焦點)。.NET 6 Performance 帖子對(duì)此有很好(hǎo)的解釋。

PGO 實現中常見的一種(zhǒng)優化是“熱/冷拆分”,其中經(jīng)常執行的方法部分(“熱”)在方法開(kāi)始時(shí)靠近在一起(qǐ),而不經(jīng)常執行的方法部分(“冷”)被移到一起(qǐ)移動到方法的末尾。這(zhè)可以更好(hǎo)地使用指令緩存并最大限度地減少可能(néng)未使用的代碼的負載。

作爲上下文,接口調度是 .NET 中最昂貴的調用類型。非虛拟方法調用是最快的,甚至更快的是可以通過(guò)内聯消除的調用。在這(zhè)種(zhǒng)情況下,動态 PGO 爲MoveNext. 第一個 — 熱的 — 是直接調用,另一個 — 冷的 — 是通過(guò). 如果最熱門的人大部分時(shí)間都(dōu)被跟注,那將(jiāng)是一場巨大的勝利。Enumerable+RangeIterator.MoveNextIEnumerator

這(zhè)就(jiù)是魔法。當 JIT 檢測此方法的第 0 層代碼時(shí),包括檢測此接口分派以跟蹤_source每次調用的具體類型。并且 JIT 發(fā)現每次調用都(dōu)在一個名爲 的類型上,這(zhè)是一個用于在實現内部實現的私有類。因此,對(duì)于第 1 層,JIT 已發(fā)出檢查以查看類型是否爲:如果不是,則它跳轉到我們之前強調的執行正常接口調度的冷部分。但如果是——基于分析數據預計在絕大多數時(shí)間都(dōu)是這(zhè)種(zhǒng)情況——然後(hòu)它可以繼續直接調用Enumerable+RangeIteratorEnumerable.RangeEnumerable_sourceEnumerable+RangeIteratorEnumerable+RangeIterator.MoveNext方法,非虛拟化。不僅如此,它還(hái)認爲内聯該MoveNext方法是有利可圖的。最終效果是生成(chéng)的彙編代碼稍大一些,但針對(duì)預期最常見的确切場景進(jìn)行了優化。當我們開(kāi)始構建動态 PGO 時(shí),這(zhè)些就(jiù)是我們想要的勝利。

動态 PGO 在 RyuJIT 部分再次讨論。

文件 IO 改進(jìn)

FileStream幾乎完全用 .NET 6 重寫,重點是提高異步文件 IO 性能(néng)。在 Windows 上,實現不再使用阻塞 API 并且可以快幾倍!我們還(hái)改進(jìn)了所有平台上的内存使用。在第一個異步操作(通常分配)之後(hòu),我們已經(jīng)使異步操作無分配!此外,當 Windows 和 Unix 實現不同(并且這(zhè)是可能(néng)的)時(shí),我們使邊緣情況的行爲變得統一。

這(zhè)種(zhǒng)重寫的性能(néng)改進(jìn)使所有操作系統受益。Windows 的好(hǎo)處是最高的,因爲它遠遠落後(hòu)。macOS 和 Linux 用戶還(hái)應該看到顯著(zhe)的FileStream性能(néng)改進(jìn)。

以下基準測試將(jiāng) 100 MB 寫入新文件。

private byte[] _bytes = new byte[8_000];

[Benchmark]
public async Task Write100MBAsync()
{
    using FileStream fs = new("file.txt", FileMode.Create, FileAccess.Write, FileShare.None, 1, FileOptions.Asynchronous);
    for (int i = 0; i < 100_000_000 / 8_000; i++)
        await fs.WriteAsync(_bytes);
}

在帶有 SSD 驅動器的 Windows 上,我們觀察到了4 倍的加速和超過(guò)1200 倍的分配下降:

我們還(hái)認識到需要更多高性能(néng)文件 IO 功能(néng):并發(fā)讀寫和分散/聚集 IO。針對(duì)這(zhè)些情況,我們爲和類引入了新的 API 。System.IO.FileSystem.IO.RandomAccess

async Task AllOrNothingAsync(string path, IReadOnlyList buffers)
{
    using SafeFileHandle handle = File.OpenHandle(
        path, FileMode.Create, FileAccess.Write, FileShare.None, FileOptions.Asynchronous,
        preallocationSize: buffers.Sum(buffer => buffer.Length)); // hint for the OS to pre-allocate disk space

    await RandomAccess.WriteAsync(handle, buffers, fileOffset: 0); // on Linux it's translated to a single sys-call!
}

示例演示:

  • 使用新API打開(kāi)文件句柄。File.OpenHandle

  • 使用新的預分配大小功能(néng)預分配磁盤空間。

  • 使用新的Scatter/Gather IOAPI寫入文件。

預分配大小功能(néng)提高了性能(néng),因爲寫入操作不需要擴展文件,而且文件碎片化的可能(néng)性較小。這(zhè)種(zhǒng)方法提高了可靠性,因爲寫操作將(jiāng)不再因空間不足而失敗,因爲空間已經(jīng)被保留。Scatter/Gather IO API 減少了寫入數據所需的系統調用次數。

更快的界面(miàn)檢查和轉換

界面(miàn)投射性能(néng)提升了 16% – 38%。這(zhè)種(zhǒng)改進(jìn)對(duì)于 C# 與接口之間的模式匹配特别有用。





該圖表展示了代表性基準的改進(jìn)規模。

將(jiāng) .NET 運行時(shí)的一部分從 C++ 遷移到托管 C# 的最大優勢之一是它降低了貢獻的障礙。這(zhè)包括接口轉換,它作爲 .NET 6 的早期更改移至 C#。.NET 生态系統中通曉 C# 的人多于 C++(并且運行時(shí)使用具有挑戰性的 C++ 模式)。能(néng)夠閱讀組成(chéng)運行時(shí)的一些代碼是培養對(duì)以各種(zhǒng)形式貢獻的信心的重要一步。

歸功于本·亞當斯

System.Text.Json 源代碼生成(chéng)器

我們爲 System.Text.Json添加了一個源代碼生成(chéng)器,它避免了在運行時(shí)進(jìn)行反射和代碼生成(chéng)的需要,并且可以在構建時(shí)生成(chéng)最佳序列化代碼。序列化程序通常使用非常保守的技術編寫,因爲它們必須如此。但是,如果您閱讀自己的序列化源代碼(使用序列化程序),您會(huì)看到哪些明顯的選擇可以使序列化程序在您的特定情況下更加優化。這(zhè)正是這(zhè)個新的源生成(chéng)器所做的。

除了提高性能(néng)和減少内存之外,源代碼生成(chéng)器還(hái)可以生成(chéng)最适合程序集修整的代碼。這(zhè)有助于開(kāi)發(fā)更小的應用程序。

序列化POCO是一個非常常見的場景。使用新源發(fā)生器,我們觀察到,序列化是〜1.6倍快與我們的基準

TechEmpower緩存基準行使平台或從數據庫來源的信息架構的内存緩存。基準測試的 .NET 實現對(duì)緩存數據執行 JSON 序列化,以便將(jiāng)其作爲對(duì)測試工具的響應發(fā)送。

我們觀察到~100K RPS 增益(~40% 增加)。與MemoryCache 性能(néng)改進(jìn)相結合時(shí),.NET 6 的吞吐量比 .NET 5 高 50% !

C# 10

歡迎使用 C# 10。C# 10 的一個主要主題是繼續從C# 9 中的頂級語句開(kāi)始的簡化之旅。新功能(néng)從 中删除了更多的儀式,導緻程序短至一行。他們的靈感來自與沒(méi)有 C# 經(jīng)驗的人(學(xué)生、專業開(kāi)發(fā)人員和其他人)交談,并學(xué)習對(duì)他們來說(shuō)最有效且直觀的方法。Program.cs

大多數.NET SDK 模闆已經(jīng)更新,以提供 C# 10 現在可以實現的更簡單、更簡潔的體驗。我們聽到反饋說(shuō)有些人不喜歡新模闆,因爲它們不是爲專家設計的,删除面(miàn)向(xiàng)對(duì)象,删除在編寫 C# 的第一天就(jiù)需要學(xué)習的重要概念,或鼓勵在一個文件中編寫整個程序。客觀地說(shuō),這(zhè)些觀點都(dōu)不是真的。新模型同樣适用于學(xué)生和專業開(kāi)發(fā)人員。然而,它與我們在 .NET 6 之前擁有的 C 派生模型不同。

C# 10 中還(hái)有其他一些功能(néng)和改進(jìn),包括記錄結構。

全局 using 指令

全局 using 指令讓您using隻需指定一次指令,并將(jiāng)其應用于您編譯的每個文件。

以下示例顯示了語法的廣度:

  • global using System;

  • global using static System.Console;

  • global using Env = System.Environment;

您可以將(jiāng)語句放在任何文件中,包括在.global using.csProgram.cs

隐式 using 是一個 MSBuild 概念,它根據 SDK自動添加一組指令。例如,控制台應用隐式使用不同于http://ASP.NETCore。global using

隐式使用是選擇加入的,并在以下位置啓用PropertyGroup:

  • enable

隐式使用是現有項目的選擇加入,但默認情況下包含在新的 C# 項目中。有關更多信息,請參閱隐式使用

文件範圍的命名空間

文件範圍的命名空間使您可以爲整個文件聲明命名空間,而無需將(jiāng)其餘内容嵌套在. 隻允許一個,并且它必須出現在聲明任何類型之前。{ ... }

新語法是一行:

namespace MyNamespace;

class MyClass { ... } // Not indented

這(zhè)種(zhǒng)新語法是三行縮進(jìn)樣式的替代:

namespace MyNamespace
{
    class MyClass { ... } // Everything is indented
}

好(hǎo)處是在整個文件都(dōu)在同一個命名空間中的極其常見的情況下減少縮進(jìn)。

記錄結構

C# 9 引入了記錄作爲類的一種(zhǒng)特殊的面(miàn)向(xiàng)值的形式。在 C# 10 中,您還(hái)可以聲明結構記錄。C# 中的結構已經(jīng)具有值相等性,但記錄結構添加了一個==運算符和一個 的實現,以及一個基于值的實現:IEquatableToString

public record struct Person
{
    public string FirstName { get; init; }
    public string LastName { get; init; }
}

就(jiù)像記錄類一樣,記錄結構可以是“位置”的,這(zhè)意味著(zhe)它們有一個主構造函數,它隐式聲明了與參數對(duì)應的公共成(chéng)員:

public record struct Person(string FirstName, string LastName);

但是,與記錄類不同,隐式公共成(chéng)員是可變的自動實現的屬性。這(zhè)是因爲記錄結構是元組的自然成(chéng)長(cháng)故事(shì)。例如,如果您有一個返回類型,并且您想將(jiāng)其擴展爲命名類型,您可以輕松聲明相應的位置結構記錄并維護可變語義。(string FirstName, string LastName)

如果您想要一個具有隻讀屬性的不可變記錄,您可以聲明整個記錄結構readonly(就(jiù)像其他結構一樣):

public readonly record struct Person(string FirstName, string LastName);

C# 10 不僅支持with記錄結構的表達式,還(hái)支持所有結構以及匿名類型的表達式:

var updatedPerson = person with { FirstName = "Mary" };

F# 6

F# 6旨在讓 F# 更簡單、更高效。這(zhè)适用于語言設計、庫和工具。我們對(duì) F# 6(及更高版本)的目标是消除語言中讓用戶感到驚訝或對(duì)學(xué)習 F# 造成(chéng)障礙的極端情況。我們很高興與 F# 社區合作進(jìn)行這(zhè)項持續的工作。

使 F# 更快、更具互操作性

新語法直接創建一個任務并啓動它。這(zhè)是 F# 6 中最重要的特性之一,它使異步任務更簡單、性能(néng)更高,并且與 C# 和其他 .NET 語言的互操作性更強。以前,創建 .NET 任務需要使用創建任務和調用.task {…}async {…}Async.StartImmediateAsTask

該功能(néng)建立在稱爲“可恢複代碼”RFC FS-1087的基礎之上。可恢複代碼是一個核心特性,我們希望在未來使用它來構建其他高性能(néng)異步和屈服狀态機。task {…}

F# 6 還(hái)爲庫作者添加了其他性能(néng)特性,包括InlineIfLambdaF# 活動模式的未裝箱表示。一個特别顯著(zhe)的性能(néng)改進(jìn)是在列表和數組表達式的編譯中,現在它們的速度提高了4 倍,并且調試也更好(hǎo)更簡單。

使 F# 更易于學(xué)習且更統一

F# 6 啓用索引語法。到目前爲止,F# 一直使用 expr.[idx] 進(jìn)行索引。删除點符号是基于第一次使用 F# 用戶的反複反饋,即使用點是與他們期望的标準實踐的不必要的分歧。在新代碼中,我們建議系統地使用新的索引語法。作爲一個社區,我們都(dōu)應該改用這(zhè)種(zhǒng)語法。expr[idx]expr[idx]

F# 社區做出了重要改進(jìn),使 F# 語言在 F# 6 中更加統一。其中最重要的是消除了 F# 縮進(jìn)規則中的許多不一緻和限制。其他使 F# 更加統一的設計添加包括添加as模式;在計算表達式中允許“重載自定義操作”(對(duì) DSL 有用);允許_丢棄use綁定并允許輸出中的二進(jìn)制格式。F# 核心庫添加了用于對(duì)列表、數組和序列進(jìn)行複制和更新的新函數,以及其他内在函數。從 2.0 開(kāi)始不推薦使用的 F# 的一些遺留功能(néng)現在會(huì)導緻錯誤。其中許多更改更好(hǎo)地使 F# 符合您的期望,從而減少了意外。%BNativePtr

F# 6 還(hái)添加了對(duì) F# 中其他“隐式”和“類型導向(xiàng)”轉換的支持。這(zhè)意味著(zhe)更少的顯式向(xiàng)上轉換,并增加了對(duì) .NET 樣式隐式轉換的一流支持。F# 還(hái)進(jìn)行了調整,以更好(hǎo)地适應使用 64 位整數的數字庫時(shí)代,并對(duì) 32 位整數進(jìn)行隐式加寬。

改進(jìn) F# 工具

F# 6 中的工具改進(jìn)使日常編碼變得更容易。新的“管道(dào)調試”允許您單步執行、設置斷點并檢查 F# 管道(dào)語法的中間值。陰影值的調試顯示已得到改進(jìn),消除了調試時(shí)常見的混淆源。現在,F# 工具的性能(néng)也更高,F# 編譯器并行執行解析階段。F# IDE 工具也得到了改進(jìn)。F# 腳本現在更加強大,允許您通過(guò)文件固定使用的 .NET SDK 版本。input |> f1 |> f2global.json

熱重載

熱重載是另一個性能(néng)特性,專注于開(kāi)發(fā)人員的生産力。它使您能(néng)夠對(duì)正在運行的應用程序進(jìn)行各種(zhǒng)代碼編輯,從而減少您等待應用程序重新構建、重新啓動或重新導航到進(jìn)行代碼更改後(hòu)所在位置所需的時(shí)間。

熱重載可通過(guò)dotnet watchCLI 工具和 Visual Studio 2022 使用。您可以將(jiāng)熱重載用于多種(zhǒng)應用程序類型,例如 ASP.NET Core、Blazor、.NET MAUI、控制台、Windows 窗體 (WinForms)、WPF、WinUI 3、Azure 函數等。

使用 CLI 時(shí),隻需使用 啓動您的 .NET 6 應用程序dotnet watch,進(jìn)行任何支持的編輯,并在保存文件時(shí)(如在 Visual Studio Code 中)將(jiāng)立即應用這(zhè)些更改。如果不支持更改,詳細信息將(jiāng)記錄到命令窗口。





此圖像顯示正在啓動的 MVC 應用程序dotnet watch。我對(duì)和文件進(jìn)行了編輯(如日志中所報告的那樣),并且兩(liǎng)者都(dōu)被應用到代碼中并在不到半秒的時(shí)間内很快地反映在浏覽器中。.cs.cshtml

使用 Visual Studio 2022 時(shí),隻需啓動您的應用程序,進(jìn)行受支持的更改,然後(hòu)使用新的“熱重載”按鈕(如下圖所示)應用這(zhè)些更改。您還(hái)可以選擇通過(guò)同一按鈕上的下拉菜單在保存時(shí)應用更改。使用 Visual Studio 2022 時(shí),Hot Reload 可用于多個 .NET 版本、.NET 5+、.NET Core 和 .NET Framework。例如,您將(jiāng)能(néng)夠對(duì)OnClickEvent按鈕的處理程序進(jìn)行代碼隐藏更改。Main應用程序的方法不支持它。





注意:RuntimeInformation.FrameworkDescription中存在一個錯誤,錯誤在該圖像中顯示,將(jiāng)很快修複。

熱重載還(hái)與現有的“編輯并繼續”功能(néng)(在斷點處停止時(shí))和 XAML 熱重載協同工作,用于實時(shí)編輯應用程序 UI。目前支持 C# 和 Visual Basic 應用程序(不是 F#)。

安全

.NET 6 中的安全性得到了顯著(zhe)改善。它始終是團隊的重要關注點,包括威脅建模、加密和縱深防禦緩解措施。

在 Linux 上,我們依賴OpenSSL進(jìn)行所有加密操作,包括 TLS(HTTPS 需要)。在 macOS 和 Windows 上,我們依賴操作系統提供的功能(néng)來實現相同的目的。對(duì)于 .NET 的每個新版本,我們經(jīng)常需要添加對(duì)新版本 OpenSSL 的支持。.NET 6 添加了對(duì)OpenSSL 3 的支持。

OpenSSL 3 的最大變化是改進(jìn)的FIPS 140-2模塊和更簡單的許可。

.NET 6 需要 OpenSSL 1.1 或更高版本,并且更喜歡它可以找到的最高安裝版本的 OpenSSL,直到并包括 v3。在一般情況下,當您使用的 Linux 發(fā)行版切換爲默認設置時(shí),您最有可能(néng)開(kāi)始使用 OpenSSL 3。大多數發(fā)行版還(hái)沒(méi)有這(zhè)樣做。例如,如果您在 Red Hat 8 或 Ubuntu 20.04 上安裝 .NET 6,您將(jiāng)不會(huì)(在撰寫本文時(shí))開(kāi)始使用 OpenSSL 3。

OpenSSL 3、Windows 10 21H1 和 Windows Server 2022 都(dōu)支持ChaCha20Poly1305。您可以在 .NET 6 中使用這(zhè)種(zhǒng)新的經(jīng)過(guò)身份驗證的加密方案(假設您的環境支持它)。

感謝凱文-瓊斯爲ChaCha20Poly1305 Linux支持。

我們還(hái)發(fā)布了新的運行時(shí)安全緩解路線圖。重要的是您使用的運行時(shí)不受教科書式攻擊類型的影響。我們正在滿足這(zhè)種(zhǒng)需求。在 .NET 6 中,我們構建了W^X英特爾控制流執行技術 (CET) 的初始實現。W^X 完全受支持,默認情況下爲 macOS Arm64 啓用,并在其他環境中選擇加入。CET 是所有環境的選擇和預覽。我們希望默認情況下爲 .NET 7 中的所有環境啓用這(zhè)兩(liǎng)種(zhǒng)技術。

ARM64

如今,對(duì)于筆記本電腦、雲硬件和其他設備,Arm64 令人興奮不已。我們對(duì) .NET 團隊也感到同樣的興奮,并正在盡最大努力跟上這(zhè)一行業趨勢。我們直接與 Arm Holdings、Apple 和 Microsoft 的工程師合作,以确保我們的實施正确且經(jīng)過(guò)優化,并且我們的計劃保持一緻。這(zhè)些密切的夥伴關系對(duì)我們幫助很大。

  • 特别感謝 Apple 在 M1 芯片發(fā)布之前向(xiàng)我們的團隊發(fā)送了一蒲式耳的 Arm64 開(kāi)發(fā)套件,并提供了重要的技術支持。

  • 特别感謝 Arm Holdings,他們的工程師代碼審查了我們的 Arm64 更改并進(jìn)行了性能(néng)改進(jìn)。

在此之前,我們通過(guò) .NET Core 3.0 和 Arm32 添加了對(duì) Arm64 的初始支持。該團隊在最近的幾個版本中都(dōu)對(duì) Arm64 進(jìn)行了重大投資,并且在可預見的未來還(hái)將(jiāng)繼續。在 .NET 6 中,我們的主要重點是在macOS 和 Windows Arm64 操作系統上支持新的 Apple Silicon 芯片和x64 仿真場景

您可以在 macOS 11+ 和 Windows 11+ Arm64 操作系統上安裝 Arm64 和 x64 版本的 .NET。我們必須做出多項設計選擇和産品更改以确保有效。

我們的策略是“親原生架構”。我們建議您始終使用與原生架構匹配的 SDK,即 macOS 和 Windows Arm64 上的 Arm64 SDK。SDK 是一個龐大的軟件體。與仿真相比,在 Arm64 芯片上本地運行的性能(néng)要高得多。我們已更新 CLI 以簡化此操作。我們永遠不會(huì)專注于優化模拟 x64。

默認情況下,如果您dotnet run使用 Arm64 SDK 的 .NET 6 應用程序,它將(jiāng)作爲 Arm64 運行。您可以使用參數輕松切換到以 x64 運行,例如. 相同的論點适用于其他 CLI 動詞。有關詳細信息,請參閱适用于 macOS 和 Windows Arm64 的 .NET 6 RC2 更新。-adotnet run -a x64

我想确保涵蓋了一個微妙之處。當您使用 時(shí),SDK 仍以 Arm64 的形式在本機運行。.NET SDK體系結構中存在進(jìn)程邊界存在的固定點。大多數情況下,一個進(jìn)程必須全是 Arm64 或全是 x64。我稍微簡化了一點,但 .NET CLI 會(huì)等待 SDK 架構中的最後(hòu)一個進(jìn)程創建,然後(hòu)將(jiāng)它作爲您請求的芯片架構啓動,例如 x64。這(zhè)就(jiù)是您的代碼運行的過(guò)程。這(zhè)樣,您作爲開(kāi)發(fā)人員可以獲得 Arm64 的好(hǎo)處,但您的代碼可以在它需要的過(guò)程中運行。這(zhè)僅在您需要以 x64 格式運行某些代碼時(shí)才相關。如果你不這(zhè)樣做,那麼(me)你可以一直以 Arm64 的方式運行一切,這(zhè)很好(hǎo)。-a x64

Arm64 支持

以下是您需要了解的關鍵點,适用于 macOS 和 Windows Arm64:

  • 支持并推薦 .NET 6 Arm64 和 x64 SDK。

  • 支持所有支持的 Arm64 和 x64 運行時(shí)。

  • .NET Core 3.1 和 .NET 5 SDK 可以工作,但提供的功能(néng)較少,并且在某些情況下不完全受支持。

  • dotnet test尚不能(néng)與 x64 仿真一起(qǐ)正常工作。我們正在爲此努力。dotnet test將(jiāng)作爲 6.0.200 版本的一部分進(jìn)行改進(jìn),可能(néng)更早。

有關更完整的信息,請參閱.NET 對(duì) macOS 和 Windows Arm64 的支持

本次讨論中缺少 Linux。它不像 macOS 和 Windows 那樣支持 x64 仿真。因此,這(zhè)些新的 CLI 特性和支持方法并不直接适用于 Linux,Linux 也不需要它們。

Windows  Arm64

我們有一個簡單的工具來演示.NET 運行的環境

C:Usersrich>dotnet tool install -g dotnet-runtimeinfo
You can invoke the tool using the following command: dotnet-runtimeinfo
Tool 'dotnet-runtimeinfo' (version '1.0.5') was successfully installed.

C:Usersrich>dotnet runtimeinfo
         42
         42              ,d                             ,d
         42              42                             42
 ,adPPYb,42  ,adPPYba, MM42MMM 8b,dPPYba,   ,adPPYba, MM42MMM
a8"    `Y42 a8"     "8a  42    42P'   `"8a a8P_____42   42
8b       42 8b       d8  42    42       42 8PP"""""""   42
"8a,   ,d42 "8a,   ,a8"  42,   42       42 "8b,   ,aa   42,
 `"8bbdP"Y8  `"YbbdP"'   "Y428 42       42  `"Ybbd8"'   "Y428

**.NET information
Version: 6.0.0
FrameworkDescription: .NET 6.0.0-rtm.21522.10
Libraries version: 6.0.0-rtm.21522.10
Libraries hash: 4822e3c3aa77eb82b2fb33c9321f923cf11ddde6

**Environment information
ProcessorCount: 8
OSArchitecture: Arm64
OSDescription: Microsoft Windows 10.0.22494
OSVersion: Microsoft Windows NT 10.0.22494.0

如您所見,該工具在 Windows Arm64 上本機運行。我將(jiāng)向(xiàng)您展示http://ASP.NETCore 的外觀。





macOS Arm64

并且您可以看到在 macOS Arm64 上的體驗是相似的,并且還(hái)展示了架構定位。

rich@MacBook-Air app % dotnet --version
6.0.100
rich@MacBook-Air app % dotnet --info | grep RID
 RID:         osx-arm64
rich@MacBook-Air app % cat Program.cs 
using System.Runtime.InteropServices;
using static System.Console;

WriteLine($"Hello, {RuntimeInformation.OSArchitecture} from {RuntimeInformation.FrameworkDescription}!");
rich@MacBook-Air app % dotnet run
Hello, Arm64 from .NET 6.0.0-rtm.21522.10!
rich@MacBook-Air app % dotnet run -a x64
Hello, X64 from .NET 6.0.0-rtm.21522.10!
rich@MacBook-Air app %

該圖展示了 Arm64 執行是 Arm64 SDK 的默認執行,以及使用參數在面(miàn)向(xiàng) Arm64 和 x64 之間切換是多麼(me)容易。完全相同的體驗适用于 Windows Arm64。-a





此圖像演示了相同的内容,但使用http://ASP.NETCore。我使用的 .NET 6 Arm64 SDK 與您在上圖中看到的相同。

Arm64 上的 Docker

Docker 支持在本機架構和仿真中運行的容器,本機架構是默認的。這(zhè)看起(qǐ)來很明顯,但當大多數 Docker Hub 目錄面(miàn)向(xiàng) x64 時(shí)可能(néng)會(huì)令人困惑。您可以使用來請求 x64 圖像。--platform linux/amd64

我們僅支持在 Arm64 操作系統上運行 Linux Arm64 .NET 容器映像。這(zhè)是因爲我們從來不支持在QEMU 中運行 .NET ,這(zhè)是 Docker 用于架構模拟的。看來這(zhè)可能(néng)是由于 QEMU 的限制





此圖片展示了我們維護控制台例子:。這(zhè)是一個有趣的示例,因爲它包含一些用于打印 CPU 和内存限制信息的基本邏輯,您可以使用它們。我展示的圖像設置了 CPU 和内存限制。mcr.microsoft.com/dotnet/samples

自己試試: docker run --rm mcr.microsoft.com/dotnet/samples

Arm64 性能(néng)

Apple Silicon 和 x64 仿真支持項目非常重要,但是,我們也普遍提高了 Arm64 性能(néng)。





此圖展示了將(jiāng)堆棧幀的内容清零方面(miàn)的改進(jìn),這(zhè)是一種(zhǒng)常見操作。綠線是新行爲,而橙色線是另一個(不太有益的)實驗,兩(liǎng)者都(dōu)相對(duì)于基線有所改進(jìn),由藍線表示。對(duì)于這(zhè)個測試,越低越好(hǎo)。

容器

.NET 6 更适合容器,主要基于本文中讨論的所有改進(jìn),适用于 Arm64 和 x64。我們還(hái)進(jìn)行了關鍵更改,這(zhè)將(jiāng)有助于各種(zhǒng)場景。使用 .NET 6 驗證容器改進(jìn)演示了其中一些改進(jìn)正在一起(qǐ)測試。

Windows 容器改進(jìn)和新的環境變量也已包含在 11 月 9 日(明天)發(fā)布的 11 月 .NET Framework 4.8 容器更新中。

發(fā)行說(shuō)明可在我們的 docker 存儲庫中找到:


Windows Docker

.NET 6 添加了對(duì) Windows 進(jìn)程隔離容器的支持。如果您在 Azure Kubernetes 服務 (AKS) 中使用Windows 容器,則您依賴于進(jìn)程隔離的容器。進(jìn)程隔離容器可以被認爲與 Linux 容器非常相似。Linux 容器使用cgroups,Windows 進(jìn)程隔離容器使用Job Objects。Windows 還(hái)提供 Hyper-V 容器,它通過(guò)更大的虛拟化提供更大的隔離。Hyper-V 容器在 .NET 6 中沒(méi)有變化。

此更改的主要價值是現在將(jiāng)報告 Windows 進(jìn)程隔離容器的正确值。如果在 64 核機器上創建 2 核容器,將(jiāng)返回. 在以前的版本中,此屬性將(jiāng)報告機器上的處理器總數,與 Docker CLI、Kubernetes 或其他容器編排器/運行時(shí)指定的限制無關。該值被 .NET 的各個部分用于縮放目的,包括 .NET 垃圾收集器(盡管它依賴于相關的較低級别的 API)。社區庫也依賴此 API 進(jìn)行擴展。Environment.ProcessorCountEnvironment.ProcessorCount2

我們最近在 AKS 上使用大量 Pod 生産的 Windows 容器上與客戶一起(qǐ)驗證了這(zhè)項新功能(néng)。他們能(néng)夠用50%的内存運行成(chéng)功(相比,其典型的配置),先前導緻了水平OutOfMemoryException和StackOverflowException例外。他們沒(méi)有花時(shí)間找到最小内存配置,但我們猜測它明顯低于他們典型内存配置的 50%。由于這(zhè)一變化,他們將(jiāng)轉向(xiàng)更便宜的 Azure 配置,從而節省資金。這(zhè)是一個不錯的、輕松的勝利,隻需升級即可。

優化縮放

我們從用戶那裡(lǐ)聽到一些應用程序在報告正确值時(shí)無法實現最佳縮放。如果這(zhè)聽起(qǐ)來與您剛剛閱讀的 Windows Containers 内容相反,那麼(me)它有點像。.NET 6 現在提供DOTNET_PROCESSOR_COUNT 環境變量來手動控制 的值。在典型用例中,應用程序可能(néng)在 64 核機器上配置有 4 個核,并且在 8 核或 16 核方面(miàn)具有最佳擴展性。此環境變量可用于啓用該縮放。Environment.ProcessorCountEnvironment.ProcessorCount

這(zhè)個模型可能(néng)看起(qǐ)來很奇怪,其中和(通過(guò) Docker CLI)值可能(néng)不同。默認情況下,容器運行時(shí)面(miàn)向(xiàng)核心等效項,而不是實際核心。這(zhè)意味著(zhe),當您說(shuō)需要 4 個内核時(shí),您將(jiāng)獲得 4 個内核的等效 CPU 時(shí)間,但是您的應用程序可能(néng)(理論上)在更多内核上運行,甚至在短時(shí)間内在 64 核機器上運行所有 64 個内核。這(zhè)可能(néng)會(huì)使您的應用程序在 4 個以上的線程上更好(hǎo)地擴展(繼續示例),并且分配更多可能(néng)是有益的。這(zhè)假設線程分配基于 的值。如果您選擇設置更高的值,您的應用可能(néng)會(huì)使用更多内存。對(duì)于某些工作負載,這(zhè)是一個簡單的權衡。至少,這(zhè)是一個您可以測試的新選項。Environment.ProcessorCount--cpusEnvironment.ProcessorCount

Linux 和 Windows 容器均支持此新功能(néng)。

Docker 還(hái)提供了 CPU 組功能(néng),您的應用程序可以關聯到特定的内核。在這(zhè)種(zhǒng)情況下不推薦使用此功能(néng),因爲應用程序可以訪問的内核數量是具體定義的。我們還(hái)看到了將(jiāng)它與 Hyper-V 容器一起(qǐ)使用時(shí)的一些問題,它并不是真正适用于這(zhè)種(zhǒng)隔離模式。

Debian 11 “靶心”

我們密切關注Linux 發(fā)行版的生命周期和發(fā)布計劃,并嘗試代表您做出最佳選擇。Debian 是我們用于默認 Linux 映像的 Linux 發(fā)行版。如果您6.0從我們的一個容器存儲庫中提取标簽,您將(jiāng)提取一個 Debian 映像(假設您使用的是 Linux 容器)。對(duì)于每個新的 .NET 版本,我們都(dōu)會(huì)考慮是否應該采用新的 Debian 版本。

作爲政策問題,我們不會(huì)爲了我們的便利标簽而更改 Debian 版本,例如6.0中期發(fā)布。如果我們這(zhè)樣做了,某些應用程序肯定會(huì)崩潰。這(zhè)意味著(zhe),在發(fā)布之初選擇 Debian 版本非常重要。此外,這(zhè)些圖像得到了很多使用,主要是因爲它們被“好(hǎo)标簽”引用。

Debian 和 .NET 版本自然不是一起(qǐ)計劃的。當我們開(kāi)始 .NET 6 時(shí),我們看到 Debian “bullseye”可能(néng)會(huì)在 2021 年發(fā)布。我們決定從發(fā)布之初就(jiù)押注于 Bullseye。我們開(kāi)始使用.NET 6 Preview 1發(fā)布基于 Bullseye 的容器映像,并決定不再回頭。賭注是 .NET 6 版本將(jiāng)在與 Bullseye 版本的競争中失敗。到 8 月 8 日,我們仍然不知道(dào) Bullseye 什麼(me)時(shí)候發(fā)貨,在我們自己的版本發(fā)布前三個月,即 11 月 8 日。我們不想在預覽版 Linux 上發(fā)布生産 .NET 6,但我們堅持到了我們會(huì)輸掉這(zhè)場比賽的計劃。

Debian 11 “bullseye”于 8 月 14 日發(fā)布時(shí),我們感到驚喜。我們輸了比賽,但赢了賭注。這(zhè)意味著(zhe) .NET 6 用戶從第一天起(qǐ)就(jiù)默認獲得最好(hǎo)和最新的 Debian。我們相信 Debian 11 和 .NET 6 將(jiāng)成(chéng)爲許多用戶的絕佳組合。對(duì)不起(qǐ),克星,我們撞到了靶心

較新的發(fā)行版在其軟件包源中包含各種(zhǒng)軟件包的較新主要版本,并且通常可以更快地獲得CVE 修複。這(zhè)是對(duì)較新内核的補充。新的發(fā)行版可以更好(hǎo)地爲用戶服務。

展望未來,不久我們將(jiāng)開(kāi)始計劃對(duì)Ubuntu 22.04 的支持。Ubuntu是另一個 Debian 家族發(fā)行版,深受 .NET 開(kāi)發(fā)人員的歡迎。我們希望爲新的 Ubuntu LTS 版本提供當日支持。

向(xiàng)Tianon Gravi 緻敬,他爲社區維護 Debian 映像并在我們遇到問題時(shí)幫助我們。

網絡監視器

dotnet monitor是容器的重要診斷工具。它作爲 sidecar 容器鏡像已經(jīng)有一段時(shí)間了,但處于不受支持的“實驗”狀态。作爲 .NET 6 的一部分,我們將(jiāng)發(fā)布一個完全支持生産的基于 .NET 6 的dotnet monitor映像

dotnet monitor已被 Azure 應用服務用作其http://ASP.NETCore Linux 診斷體驗的實現細節。這(zhè)是預期的場景之一,建立在 dotnet monitor 之上以提供更高級别和更高價值的體驗。

您現在可以拉取新圖像:

docker pull mcr.microsoft.com/dotnet/monitor:6.0

dotnet monitor使從 .NET 進(jìn)程訪問診斷信息(日志、跟蹤、進(jìn)程轉儲)變得更加容易。在台式機上很容易訪問您想要的所有診斷信息,但是,例如,那些熟悉的技術在使用容器的生産中可能(néng)不起(qǐ)作用。dotnet monitor提供了一種(zhǒng)統一的方法來收集這(zhè)些診斷工件,無論是在您的台式機上還(hái)是在 Kubernetes 集群中運行。收集這(zhè)些診斷工件有兩(liǎng)種(zhǒng)不同的機制:

  • 用于臨時(shí)收集工件的HTTP API。當您已經(jīng)知道(dào)您的應用程序遇到問題并且您有興趣收集更多信息時(shí),您可以調用這(zhè)些 API 端點。

  • 基于規則的配置觸發(fā)器,用于始終在線的工件集合。您可以配置規則以在滿足所需條件時(shí)收集診斷數據,例如,在持續高 CPU 時(shí)收集進(jìn)程轉儲。

dotnet monitor爲 .NET 應用程序提供了一個通用的診斷 API,可以使用任何工具在任何地方使用。“通用 API”不是 .NET API,而是您可以調用和查詢的 Web API。dotnet monitor包括一個 ASP.NET Web 服務器,它直接與 .NET 運行時(shí)中的診斷服務器交互并公開(kāi)數據。的設計dotnet monitor支持生産中的高性能(néng)監控和安全使用,以控制對(duì)特權信息的訪問。dotnet monitor通過(guò)非互聯網可尋址的unix 域套接字與運行時(shí)交互——跨越容器邊界。該模型通信模型非常适合此用例。

結構化JSON 日志

JSON格式現在是默認控制台記錄f="https://hub.docker.com/_/microsoft-dotnet-aspnet">aspnet.NET 6容器圖像。.NET 5 中的默認設置爲簡單的控制台格式化程序。進(jìn)行此更改是爲了擁有一個默認配置,該配置可與依賴于機器可讀格式(如 JSON)的自動化工具配合使用。

圖像的輸出現在如下所示aspnet:

$ docker run --rm -it -p 8000:80 mcr.microsoft.com/dotnet/samples:aspnetapp
{"EventId":60,"LogLevel":"Warning","Category":"Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository","Message":"Storing keys in a directory u0027/root/.aspnet/DataProtection-Keysu0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.","State":{"Message":"Storing keys in a directory u0027/root/.aspnet/DataProtection-Keysu0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.","path":"/root/.aspnet/DataProtection-Keys","{OriginalFormat}":"Storing keys in a directory u0027{path}u0027 that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed."}}
{"EventId":35,"LogLevel":"Warning","Category":"Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager","Message":"No XML encryptor configured. Key {86cafacf-ab57-434a-b09c-66a929ae4fd7} may be persisted to storage in unencrypted form.","State":{"Message":"No XML encryptor configured. Key {86cafacf-ab57-434a-b09c-66a929ae4fd7} may be persisted to storage in unencrypted form.","KeyId":"86cafacf-ab57-434a-b09c-66a929ae4fd7","{OriginalFormat}":"No XML encryptor configured. Key {KeyId:B} may be persisted to storage in unencrypted form."}}
{"EventId":14,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Now listening on: http://[::]:80","State":{"Message":"Now listening on: http://[::]:80","address":"http://[::]:80","{OriginalFormat}":"Now listening on: {address}"}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Application started. Press Ctrlu002BC to shut down.","State":{"Message":"Application started. Press Ctrlu002BC to shut down.","{OriginalFormat}":"Application started. Press Ctrlu002BC to shut down."}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Hosting environment: Production","State":{"Message":"Hosting environment: Production","envName":"Production","{OriginalFormat}":"Hosting environment: {envName}"}}
{"EventId":0,"LogLevel":"Information","Category":"Microsoft.Hosting.Lifetime","Message":"Content root path: /app","State":{"Message":"Content root path: /app","contentRoot":"/app","{OriginalFormat}":"Content root path: {contentRoot}"}}

可以通過(guò)設置或取消設置Logging__Console__FormatterName環境變量或通過(guò)代碼更改來更改記錄器格式類型(有關更多詳細信息,請參閱控制台日志格式)。

更改後(hòu),您將(jiāng)看到如下輸出(就(jiù)像 .NET 5):

$ docker run --rm -it -p 8000:80 -e Logging__Console__FormatterName="" mcr.microsoft.com/dotnet/samples:aspnetapp
warn: Microsoft.AspNetCore.DataProtection.Repositories.FileSystemXmlRepository[60]
      Storing keys in a directory '/root/.aspnet/DataProtection-Keys' that may not be persisted outside of the container. Protected data will be unavailable when container is destroyed.
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
      No XML encryptor configured. Key {8d4ddd1d-ccfc-4898-9fe1-3e7403bf23a0} may be persisted to storage in unencrypted form.
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://[::]:80
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /app

注意:此更改不會(huì)影響開(kāi)發(fā)人員計算機上的 .NET SDK,與dotnet run. 此更改特定于aspnet容器映像。

支持 OpenTelemetry 指标

我們一直在爲最近的幾個 .NET 版本添加對(duì) OpenTelemetry 的支持,作爲我們對(duì)可觀察性的關注的一部分。在 .NET 6 中,我們添加了對(duì)OpenTelemetry Metrics API 的支持。通過(guò)添加對(duì) OpenTelemetry 的支持,您的應用程序可以與其他OpenTelemetry系統無縫互操作。

System.Diagnostics.MetricsOpenTelemetry Metrics API 規範的 .NET 實現。Metrics API 是專門爲處理原始測量而設計的,目的是高效且同時(shí)地生成(chéng)這(zhè)些測量的連續摘要。

API 包括Meter可用于創建儀器對(duì)象的類。這(zhè)些API暴露四個儀器類:Counter,Histogram,ObservableCounter,并ObservableGauge支持不同的指标方案。此外,API 公開(kāi)MeterListener該類以允許偵聽儀器的記錄測量以進(jìn)行聚合和分組。

OpenTelemetry .NET實現將(jiāng)擴展到使用這(zhè)些新的API,其中新增的指标可觀察方案的支持。

庫測量記錄示例

    Meter meter = new Meter("io.opentelemetry.contrib.mongodb", "v1.0");
    Counter counter = meter.CreateCounter("Requests");
    counter.Add(1);
    counter.Add(1, KeyValuePair.Create("request", "read"));

聽力示例

    MeterListener listener = new MeterListener();
    listener.InstrumentPublished = (instrument, meterListener) =>
    {
        if (instrument.Name == "Requests" && instrument.Meter.Name == "io.opentelemetry.contrib.mongodb")
        {
            meterListener.EnableMeasurementEvents(instrument, null);
        }
    };
    listener.SetMeasurementEventCallback((instrument, measurement, tags, state) =>
    {
        Console.WriteLine($"Instrument: {instrument.Name} has recorded the measurement {measurement}");
    });
    listener.Start();

Windows 窗體

我們繼續在 Windows 窗體中進(jìn)行關鍵改進(jìn)。.NET 6 包括更好(hǎo)的控件可訪問性、設置應用程序範圍默認字體的能(néng)力、模闆更新等。

輔助功能(néng)改進(jìn)

在此版本中,我們增加了UIA提供商爲CheckedListBox,LinkLabel,Panel,ScrollBar,TabControl和TrackBar,使像旁白工具和測試自動化與應用程序的元素進(jìn)行交互。

默認字體

現在,您可以設置默認字體的應用程序有。Application.SetDefaultFont

void Application.SetDefaultFont(Font font)

最少的應用

以下是帶有 .NET 6最小 Windows 窗體應用程序

class Program
{
    [STAThread]
    static void Main()
    {
        ApplicationConfiguration.Initialize();
        Application.Run(new Form1());
    }
}

作爲 .NET 6 版本的一部分,我們一直在更新大多數模闆,使其更加現代和簡約,包括 Windows 窗體。我們決定讓 Windows 窗體模闆更傳統一些,部分原因是需要將(jiāng)該屬性應用于應用程序入口點。然而,除了立即出現之外,還(hái)有更多的玩法。[STAThread]

ApplicationConfiguration.Initialize() 是一個源代碼生成(chéng)的 API,它在後(hòu)台發(fā)出以下調用:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.SetDefaultFont(new Font(...));
Application.SetHighDpiMode(HighDpiMode.SystemAware);

這(zhè)些調用的參數可通過(guò)csproj 或 props 文件中的MSBuild 屬性進(jìn)行配置。

Visual Studio 2022 中的 Windows 窗體設計器也知道(dào)這(zhè)些屬性(目前它隻讀取默認字體),并且可以向(xiàng)您展示您的應用程序,就(jiù)像它在運行時(shí)一樣:





模闆更新

C# 的 Windows 窗體模闆已更新,以支持新的應用程序引導程序、指令、文件範圍的命名空間和可爲空的引用類型。global using

更多運行時(shí)設計器

現在您可以構建通用設計器(例如,報表設計器),因爲 .NET 6 具有設計器和設計器相關基礎結構的所有缺失部分。有關更多信息,請參閱此博客文章

單文件應用程序

在 .NET 6 中,已爲 Windows 和 macOS 啓用内存中單文件應用程序。在 .NET 5 中,這(zhè)種(zhǒng)部署類型僅限于 Linux。您現在可以爲所有支持的操作系統發(fā)布作爲單個文件部署和啓動的單文件二進(jìn)制文件。單文件應用程序不再將(jiāng)任何核心運行時(shí)程序集提取到臨時(shí)目錄。

這(zhè)種(zhǒng)擴展功能(néng)基于稱爲“超級主機”的構建塊。“apphost”是在非單文件情況下啓動應用程序的可執行文件,例如或。Apphost 包含用于查找運行時(shí)、加載它并使用該運行時(shí)啓動您的應用程序的代碼。Superhost 仍然執行其中一些任務,但使用所有 CoreCLR 本機二進(jìn)制文件的靜态鏈接副本。靜态鏈接是我們用來啓用單個文件體驗的方法。myapp.exe./myapp

本機依賴項(如 NuGet 包附帶的依賴項)是單文件嵌入的顯著(zhe)例外。默認情況下,它們不包含在單個文件中。例如,WPF 本機依賴項不是超級主機的一部分,導緻除了單文件應用程序之外還(hái)有其他文件。您可以使用該設置IncludeNativeLibrariesForSelfExtract來嵌入和提取本機依賴項。

靜态分析

我們改進(jìn)了單文件分析器以允許自定義警告。如果您的 API 在單文件發(fā)布中不起(qǐ)作用,您現在可以使用該屬性對(duì)其進(jìn)行标記,如果啓用了分析器,則會(huì)出現警告。添加該屬性還(hái)將(jiāng)使方法中與單個文件相關的所有警告靜音,因此您可以使用該警告將(jiāng)警告向(xiàng)上傳播到您的公共 API。[RequiresAssemblyFiles]

設置爲 時(shí),會(huì)自動爲exe項目啓用單文件分析器,但您也可以通過(guò)設置爲爲任何項目啓用它。如果您想支持庫作爲單個文件應用程序的一部分,這(zhè)會(huì)很有幫助。PublishSingleFiletrueEnableSingleFileAnalysistrue

在 .NET 5 中,我們添加了警告和其他一些在單文件包中表現不同的 API。Assembly.Location

壓縮

單文件包現在支持壓縮,可以通過(guò)將(jiāng)屬性設置EnableCompressionInSingleFile爲true. 在運行時(shí),文件會(huì)根據需要解壓縮到内存中。壓縮可以爲某些場景提供巨大的空間節省。

讓我們看一下與NuGet 包資源管理器一起(qǐ)使用的帶壓縮和不帶壓縮的單個文件發(fā)布。

無壓縮:172 MB





壓縮後(hòu):71.6 MB





壓縮可以顯著(zhe)增加應用程序的啓動時(shí)間,尤其是在 Unix 平台上。Unix 平台具有無法與壓縮一起(qǐ)使用的無複制快速啓動路徑。您應該在啓用壓縮後(hòu)測試您的應用,看看額外的啓動成(chéng)本是否可以接受。

單文件調試

單文件應用程序目前隻能(néng)使用平台調試器(如 WinDBG)進(jìn)行調試。我們正在考慮在 Visual Studio 2022 的更高版本中添加 Visual Studio 調試。

macOS 上的單文件簽名

單文件應用程序現在滿足 macOS 上的 Apple 公證和簽名要求。在具體的變化涉及到我們在離散文件布局方面(miàn)構建單個文件的應用程序的方式。

蘋果開(kāi)始執行新規定簽署和公證MacOS的卡特琳娜。我們一直在與 Apple 密切合作,以了解要求,并尋找解決方案,使 .NET 等開(kāi)發(fā)平台能(néng)夠在該環境中良好(hǎo)運行。在最近的幾個 .NET 版本中,我們已經(jīng)對(duì)産品進(jìn)行了更改并記錄了用戶工作流程,以滿足 Apple 的要求。剩下的差距之一是單文件簽名,這(zhè)是在 macOS 上分發(fā) .NET 應用程序的要求,包括在 macOS 商店中。

IL 修整

該團隊一直緻力于爲多個版本進(jìn)行 IL 修整。.NET 6 代表了這(zhè)一旅程的重要一步。我們一直在努力使更激進(jìn)的修剪模式安全且可預測,因此有信心將(jiāng)其設爲默認值。以前是選擇加入功能(néng),現在是默認功能(néng)。TrimMode=link

我們有一個三管齊下的修剪策略:

  • 提高平台的修剪能(néng)力。

  • 對(duì)平台進(jìn)行注釋以提供更好(hǎo)的警告并使其他人也能(néng)這(zhè)樣做。

  • 在此基礎上,使默認修剪模式更加激進(jìn),以便輕松將(jiāng)應用程序變小。

由于使用未注釋反射的應用程序的結果不可靠,因此修剪之前一直處于預覽狀态。有了修剪警告,體驗現在應該是可預測的。沒(méi)有修剪警告的應用程序應該正确修剪并且在運行時(shí)觀察到行爲沒(méi)有變化。目前,隻有核心 .NET 庫已經(jīng)完全注釋用于修剪,但我們希望看到生态系統注釋用于修剪并成(chéng)爲修剪兼容

減少應用程序大小

讓我們來看看使用crossgen 的這(zhè)種(zhǒng)修剪改進(jìn),它是 SDK 工具之一。可以隻用一些修剪警告來修剪它,crossgen 團隊能(néng)夠解決這(zhè)些問題。

首先,讓我們將(jiāng) crossgen 發(fā)布爲一個獨立的應用程序,無需修剪。它是 80 MB(包括 .NET 運行時(shí)和所有庫)。





然後(hòu)我們可以嘗試(現在是舊版).NET 5 默認修剪模式,copyused. 結果下降到 55 MB。





新的 .NET 6 默認修剪模式link將(jiāng)自包含文件大小進(jìn)一步降低到 36MB。





我們希望新的link修剪模式能(néng)更好(hǎo)地滿足修剪的期望:顯著(zhe)的節省和可預測的結果。

默認啓用警告

修剪警告告訴您修剪可能(néng)會(huì)删除運行時(shí)使用的代碼的地方。這(zhè)些警告以前在默認情況下被禁用,因爲警告非常嘈雜,主要是由于 .NET 平台沒(méi)有作爲第一類場景參與修剪。

我們對(duì) .NET 庫的大部分進(jìn)行了注釋,以便它們生成(chéng)準确的修剪警告。因此,我們認爲是時(shí)候默認啓用修剪警告了。http://ASP.NETCore 和 Windows 桌面(miàn)運行時(shí)庫尚未注釋。我們計劃接下來(.NET 6 之後(hòu))注釋http://ASP.NET服務組件。我們希望看到社區在 .NET 6 發(fā)布後(hòu)對(duì) NuGet 庫進(jìn)行注釋。

您可以通過(guò)設置爲來禁用警告true。

更多信息:

與本機 AOT 共享

我們也爲Native AOT 實驗實施了相同的修剪警告,這(zhè)應該會(huì)以大緻相同的方式改善 Native AOT 編譯體驗。

數學(xué)

我們顯著(zhe)改進(jìn)了數學(xué) API。社區一些人已經(jīng)在享受這(zhè)些改進(jìn)

面(miàn)向(xiàng)性能(néng)的 API

面(miàn)向(xiàng)性能(néng)的數學(xué) API 已添加到 System.Math。如果底層硬件支持,它們的實現是硬件加速的。

新 API:

  • SinCos用于同時(shí)計算Sin和Cos。

  • ReciprocalEstimate用于計算 的近似值。1 / x

  • ReciprocalSqrtEstimate用于計算 的近似值。1 / Sqrt(x)

新的重載:

  • Clamp、DivRem、Min、 和Max支持nint和nuint。

  • Abs和Sign支持nint。

  • DivRem返回 a 的變體tuple。

性能(néng)改進(jìn):

大整數性能(néng)

從十進(jìn)制和十六進(jìn)制字符串解析 BigIntegers已得到改進(jìn)。我們看到了高達 89% 的改進(jìn),如下圖所示(越低越好(hǎo))。





歸功于約瑟夫·達席爾瓦

Complex API 現在注釋爲 readonly

href="https://github.com/dotnet/runtime/pull/51797/">現在System.Numerics.Complexreadonly對(duì)各種(zhǒng)API 進(jìn)行了注釋,以确保不會(huì)爲readonly通過(guò)in.

感謝hrrrrustic

BitConverter 現在支持浮點到無符号整數比特轉換

BitConverter ref="https://github.com/dotnet/runtime/pull/53784">現在支持DoubleToUInt64Bits,HalfToUInt16Bits,SingleToUInt32Bits,UInt16BitsToHalf,UInt32BitsToSingle,和UInt64BitsToDouble。這(zhè)應該可以在需要時(shí)更容易地進(jìn)行浮點位操作。

感謝米哈爾Petryka

BitOperations 支持附加功能(néng)

BitOperations現在支持IsPow2RoundUpToPowerOf2爲現有函數href="https://github.com/dotnet/runtime/pull/58733">提供nint/nuint重載。

感謝約翰·凱利霍耀元羅賓·林德納

Vector, Vector2, Vector3, 和Vector4改進(jìn)

Vectorref="https://github.com/dotnet/runtime/pull/50832">現在支持nintnuintC# 9 中添加原始類型。例如,此更改應該可以更輕松地使用帶有指針或平台相關長(cháng)度類型的 SIMD 指令。

Vectorf="https://github.com/dotnet/runtime/pull/53527">現在支持一種(zhǒng)Sum方法來簡化需要計算向(xiàng)量中所有元素的“水平總和”。感謝伊萬茲拉塔諾夫

Vector"https://githhttp://ub.com/dotnet/runtime/pull/47150">現在支持通用方法As來簡化在具體類型未知的通用上下文中處理向(xiàng)量。感謝霍耀元

ref="https://github.com/dotnet/runtime/pull/50062">重載支持Span已添加到Vector2、Vector3和Vector4以改善需要加載或存儲向(xiàng)量類型時(shí)的體驗。

更好(hǎo)地解析标準數字格式

我們改進(jìn)了标準數字類型的解析器,特别是和。他們現在將(jiāng)理解精度要求 > 99 位小數,并將(jiāng)提供精确到那麼(me)多位數的結果。此外,解析器現在更好(hǎo)地支持方法中的尾随零。.ToString.TryFormatParse

以下示例演示了前後(hòu)行爲。

  • 32.ToString("C100") -> C132

    • .NET 6: $32.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

    • .NET 5:我們在格式化代碼中存在人爲限制,隻能(néng)處理 <= 99="">= 100,我們將(jiāng)輸入解釋爲自定義格式。


  • 32.ToString("H99") -> 扔一個 FormatException

    • .NET 6:抛出 FormatException

    • 這(zhè)是正确的行爲,但在此處調用它是爲了與下一個示例進(jìn)行對(duì)比。


  • 32.ToString("H100") -> H132

    • .NET 6:抛出 FormatException

    • .NET 5:H是無效的格式說(shuō)明符。所以,我們應該抛出一個FormatException. 相反,我們將(jiāng)精度 >= 100 解釋爲自定義格式的錯誤行爲意味著(zhe)我們返回了錯誤的值。


  • double.Parse("9007199254740997.0") -> 9007199254740998

    • .NET 6: 9007199254740996.

    • .NET 5:9007199254740997.0不能(néng)完全以 IEEE 754 格式表示。使用我們當前的舍入方案,正确的返回值應該是9007199254740996。但是,輸入的最後(hòu)一部分迫使解析器錯誤地舍入結果并返回。.09007199254740998


系統.文本.Json

System.Text.Json提供了多種(zhǒng)高性能(néng)的 API 來處理 JSON 文檔。在過(guò)去的幾個版本中,我們添加了新功能(néng),以進(jìn)一步提高 JSON 處理性能(néng)并減輕想要從. 此版本包括在這(zhè)條道(dào)路上的繼續,并且是性能(néng)的重大進(jìn)步,特别是在序列化器源生成(chéng)器方面(miàn)。NewtonSoft.Json

JsonSerializer 源代碼生成(chéng)

注意:應重新編譯使用 .NET 6 RC1 或更早版本的源代碼生成(chéng)的應用程序。

幾乎所有 .NET 序列化程序的支柱都(dōu)是反射。反射對(duì)于某些場景來說(shuō)是一項很棒的功能(néng),但不能(néng)作爲高性能(néng)雲原生應用程序(通常(反)序列化和處理大量 JSON 文檔)的基礎。反射是啓動、内存使用和程序集修整的問題

運行時(shí)反射的替代方案是編譯時(shí)源代碼生成(chéng)。在 .NET 6 中,我們將(jiāng)一個"https://devblogs.microsoft.com/dotnet/try-the-new-system-text-json-source-generator/">新的源生成(chéng)器作爲System.Text.Json. JSON 源生成(chéng)器可與JsonSerializer多種(zhǒng)方式結合使用并可進(jìn)行配置。

它可以提供以下好(hǎo)處:

  • 減少啓動時(shí)間

  • 提高序列化吞吐量

  • 減少私有内存使用

  • 删除運行時(shí)使用和System.ReflectionSystem.Reflection.Emit

  • IL 修整兼容性

默認情況下,JSON 源生成(chéng)器爲給定的可序列化類型發(fā)出序列化邏輯。JsonSerializer通過(guò)生成(chéng)Utf8JsonWriter直接使用的源代碼,這(zhè)提供了比使用現有方法更高的性能(néng)。簡而言之,源代碼生成(chéng)器提供了一種(zhǒng)在編譯時(shí)爲您提供不同實現的方法,以便使運行時(shí)體驗更好(hǎo)。

給定一個簡單類型:

namespace Test
{
    internal class JsonMessage
    {
        public string Message { get; set; }
    }
}

源生成(chéng)器可以配置爲爲示例JsonMessage類型的實例生成(chéng)序列化邏輯。請注意,類名JsonContext是任意的。您可以爲生成(chéng)的源使用任何您想要的類名。

using System.Text.Json.Serialization;

namespace Test
{
    [JsonSerializable(typeof(JsonMessage)]
    internal partial class JsonContext : JsonSerializerContext
    {
    }
}

使用此模式的序列化程序調用可能(néng)類似于以下示例。此示例提供了可能(néng)的最佳性能(néng)。

using MemoryStream ms = new();
using Utf8JsonWriter writer = new(ms);

JsonSerializer.Serialize(jsonMessage, JsonContext.Default.JsonMessage);
writer.Flush();

// Writer contains:
// {"Message":"Hello, world!"}

最快和最優化的源代碼生成(chéng)模式——基于Utf8JsonWriter——目前僅可用于序列化。Utf8JsonReader根據您的反饋,未來可能(néng)會(huì)提供類似的反序列化支持——基于——。

源生成(chéng)器還(hái)發(fā)出類型元數據初始化邏輯,這(zhè)也有利于反序列化。要反序列化JsonMessage使用預生成(chéng)類型元數據的實例,您可以執行以下操作:

JsonSerializer.Deserialize(json, JsonContext.Default.JsonMessage);

JsonSerializer 支持 IAsyncEnumerable

現在,可以(反)序列化JSON陣列IAsyncEnumerable與。以下示例使用流作爲數據的任何異步源的表示。源可以是本地機器上的文件,也可以是數據庫查詢或 Web 服務 API 調用的結果。System.Text.Json

JsonSerializer.SerializeAsync已更新以識别IAsyncEnumerable值并提供特殊處理。

using System;
using System.Collections.Generic;
using System.IO;
using System.Text.Json;

static async IAsyncEnumerable PrintNumbers(int n)
{
    for (int i = 0; i < n; i++) yield return i;
}

using Stream stream = Console.OpenStandardOutput();
var data = new { Data = PrintNumbers(3) };
await JsonSerializer.SerializeAsync(stream, data); // prints {"Data":[0,1,2]}

IAsyncEnumerable值僅支持使用異步序列化方法。嘗試使用同步方法進(jìn)行序列化將(jiāng)導緻NotSupportedException抛出異常。

流式反序列化需要一個新的 API 返回. 我們爲此目的添加了該方法,您可以在以下示例中看到。IAsyncEnumerableJsonSerializer.DeserializeAsyncEnumerable

using System;
using System.IO;
using System.Text;
using System.Text.Json;

var stream = new MemoryStream(Encoding.UTF8.GetBytes("[0,1,2,3,4]"));
await foreach (int item in JsonSerializer.DeserializeAsyncEnumerable(stream))
{
    Console.WriteLine(item);
}

此示例將(jiāng)按需反序列化元素,并且在使用特别大的數據流時(shí)非常有用。它隻支持從根級 JSON 數組中讀取,盡管將(jiāng)來可能(néng)會(huì)根據反饋放寬。

現有DeserializeAsync方法名義上支持,但在其非流式方法簽名的範圍内。它必須將(jiāng)最終結果作爲單個值返回,如下例所示。IAsyncEnumerable

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;

var stream = new MemoryStream(Encoding.UTF8.GetBytes(@"{""Data"":[0,1,2,3,4]}"));
var result = await JsonSerializer.DeserializeAsync(stream);
await foreach (int item in result.Data)
{
    Console.WriteLine(item);
}

public class MyPoco
{
    public IAsyncEnumerable Data { get; set; }
}

在此示例中,反序列化器將(jiāng)IAsyncEnumerable在返回反序列化對(duì)象之前緩沖内存中的所有内容。這(zhè)是因爲反序列化器需要在返回結果之前消耗整個 JSON 值。

System.Text.Json:可寫 DOM 功能(néng)

寫JSON DOM功能(néng)增加了一個新的簡單的,高性能(néng)的編程模型的。這(zhè)個新的 API 很有吸引力,因爲它避免了需要強類型的序列化契約,并且與現有類型相比,DOM 是可變的。System.Text.JsonJsonDocument

這(zhè)個新的 API 有以下好(hǎo)處:

  • 在不可能(néng)或不希望使用POCO類型的情況下,或者當 JSON 模式不固定且必須檢查時(shí),序列化的輕量級替代方案。

  • 允許對(duì)大樹的子集進(jìn)行有效修改。例如,可以高效地導航到大型 JSON 樹的子部分并從該子部分讀取數組或反序列化 POCO。LINQ 也可以與它一起(qǐ)使用。

以下示例演示了新的編程模型。

    // Parse a JSON object
    JsonNode jNode = JsonNode.Parse("{"MyProperty":42}");
    int value = (int)jNode["MyProperty"];
    Debug.Assert(value == 42);
    // or
    value = jNode["MyProperty"].GetValue();
    Debug.Assert(value == 42);

    // Parse a JSON array
    jNode = JsonNode.Parse("[10,11,12]");
    value = (int)jNode[1];
    Debug.Assert(value == 11);
    // or
    value = jNode[1].GetValue();
    Debug.Assert(value == 11);

    // Create a new JsonObject using object initializers and array params
    var jObject = new JsonObject
    {
        ["MyChildObject"] = new JsonObject
        {
            ["MyProperty"] = "Hello",
            ["MyArray"] = new JsonArray(10, 11, 12)
        }
    };

    // Obtain the JSON from the new JsonObject
    string json = jObject.ToJsonString();
    Console.WriteLine(json); // {"MyChildObject":{"MyProperty":"Hello","MyArray":[10,11,12]}}

    // Indexers for property names and array elements are supported and can be chained
    Debug.Assert(jObject["MyChildObject"]["MyArray"][1].GetValue() == 11);

ReferenceHandler.IgnoreCycles

JsonSerializer(System.Text.Json)現在支持在序列化對(duì)象圖時(shí)忽略循環的能(néng)力。該選項的行爲與 Newtonsoft.Json 相似。一個主要區别是 System.Text.Json 實現用JSON 标記替換引用循環,而不是忽略對(duì)象引用。ReferenceHandler.IgnoreCyclesReferenceLoopHandling.Ignorenull

您可以在以下示例中看到 的行爲。在這(zhè)種(zhǒng)情況下,屬性被序列化,因爲它否則會(huì)創建一個循環。ReferenceHandler.IgnoreCyclesNextnull

class Node
{
    public string Description { get; set; }
    public object Next { get; set; }
}

void Test()
{
    var node = new Node { Description = "Node 1" };
    node.Next = node;

    var opts = new JsonSerializerOptions { ReferenceHandler = ReferenceHandler.IgnoreCycles };

    string json = JsonSerializer.Serialize(node, opts);
    Console.WriteLine(json); // Prints {"Description":"Node 1","Next":null}
}

源代碼構建

使用源代碼構建,隻需使用幾條命令即可在您自己的機器上從源代碼構建 .NET SDK。讓我解釋一下爲什麼(me)這(zhè)個項目很重要。

自 .NET Core 1.0 發(fā)布之前,源代碼構建是一個場景,也是我們與 Red Hat 合作開(kāi)發(fā)的基礎設施。幾年後(hòu),我們非常接近提供它的完全自動化版本。對(duì)于 Red Hat Enterprise Linux (RHEL) .NET 用戶來說(shuō),此功能(néng)非常重要。紅帽告訴我們,.NET 已經(jīng)發(fā)展成(chéng)爲其生态系統的重要開(kāi)發(fā)者平台。好(hǎo)的!

Linux 發(fā)行版黃金标準是使用作爲發(fā)行版存檔一部分的編譯器和工具鏈構建開(kāi)源代碼。這(zhè)适用于 .NET 運行時(shí)(用 C++ 編寫),但不适用于任何用 C# 編寫的代碼。對(duì)于 C# 代碼,我們使用兩(liǎng)遍構建機制來滿足發(fā)行版要求。這(zhè)有點複雜,但了解流程很重要。

Red Hat 使用 .NET SDK (#1) 的 Microsoft 二進(jìn)制構建來構建 .NET SDK 源代碼,以生成(chéng) SDK (#2) 的純開(kāi)源二進(jìn)制構建。之後(hòu),使用 SDK 的新版本 (#2) 再次構建相同的 SDK 源代碼,以生成(chéng)可證明的開(kāi)源 SDK (#3)。.NET SDK (#3) 的最終二進(jìn)制版本随後(hòu)可供 RHEL 用戶使用。之後(hòu),Red Hat 可以使用相同的 SDK (#3) 來構建新的 .NET 版本,而不再需要使用 Microsoft SDK 來構建月度更新。

這(zhè)個過(guò)程可能(néng)令人驚訝和困惑。開(kāi)源發(fā)行版需要由開(kāi)源工具構建。此模式确保不需要 Microsoft 構建的 SDK,無論是有意還(hái)是無意。作爲開(kāi)發(fā)者平台,被包含在發(fā)行版中比僅使用兼容許可證有更高的标準。源構建項目使 .NET 能(néng)夠滿足這(zhè)一要求。

源代碼構建的交付物是一個源代碼壓縮包。源 tarball 包含 SDK(對(duì)于給定版本)的所有源。從那裡(lǐ),紅帽(或其他組織)可以構建自己的 SDK 版本。Red Hat 策略需要使用從源代碼構建的工具鏈來生成(chéng)二進(jìn)制 tar 球,這(zhè)就(jiù)是他們使用兩(liǎng)遍方法的原因。但是源代碼構建本身不需要這(zhè)種(zhǒng)兩(liǎng)遍方法。

在 Linux 生态系統中,爲給定組件提供源代碼和二進(jìn)制包或 tarball 是很常見的。我們已經(jīng)有可用的二進(jìn)制 tarball,現在也有源 tarball。這(zhè)使得 .NET 與标準組件模式相匹配。

.NET 6 的重大改進(jìn)是源 tarball 現在是我們構建的産品。過(guò)去,它需要大量的人工來制作,這(zhè)也導緻將(jiāng)源 tarball 傳送到 Red Hat 的延遲很長(cháng)。雙方對(duì)此都(dōu)不滿意。

五年多以來,我們一直與紅帽在這(zhè)個項目上密切合作。它取得了成(chéng)功,在很大程度上要歸功于我們有幸與之共事(shì)的優秀紅帽工程師的努力。其他發(fā)行版和組織已經(jīng)并將(jiāng)從他們的努力中受益。

附帶說(shuō)明一下,源代碼構建是朝著(zhe)可重現構建邁出的一大步,我們也堅信這(zhè)一點。.NET SDK 和 C# 編譯器具有重要的可重現構建功能(néng)。

庫 API

除了已經(jīng)涵蓋的 API 之外,還(hái)添加了以下 API。

WebSocket 壓縮

壓縮對(duì)于通過(guò)網絡傳輸的任何數據都(dōu)很重要。WebSockets 現在啓用壓縮。我們使用了WebSockets的擴展實現,RFC 7692。它允許使用該算法壓縮 WebSockets 消息有效負載。此功能(néng)是用戶對(duì) GitHub 上 Networking 的最高要求之一。permessage-deflateDEFLATE

與加密一起(qǐ)使用的壓縮可能(néng)會(huì)導緻攻擊,例如CRIMEBREACH。這(zhè)意味著(zhe)不能(néng)在單個壓縮上下文中將(jiāng)秘密與用戶生成(chéng)的數據一起(qǐ)發(fā)送,否則可以提取該秘密。爲了讓用戶注意這(zhè)些影響并幫助他們權衡風險,我們將(jiāng)其中一個關鍵 API 命名爲DangerousDeflateOptions。我們還(hái)添加了對(duì)特定消息關閉壓縮的功能(néng),因此如果用戶想要發(fā)送機密,他們可以在不壓縮的情況下安全地發(fā)送。

禁用壓縮時(shí) WebSocket内存占用減少了約 27%。

從客戶端啓用壓縮很容易,如下例所示。但是,請記住,服務器可以協商設置,例如請求較小的窗口或完全拒絕壓縮。

var cws = new ClientWebSocket();
cws.Options.DangerousDeflateOptions = new WebSocketDeflateOptions()
{
    ClientMaxWindowBits = 10,
    ServerMaxWindowBits = 10
};

還(hái)添加了對(duì) ASP.NET Core 的 WebSocket 壓縮支持

感謝伊萬茲拉塔諾夫

襪子代理支持

SOCKS是一種(zhǒng)代理服務器實現,可以處理任何 TCP 或 UDP 流量,使其成(chéng)爲一個非常通用的系統。這(zhè)是一個長(cháng)期存在的社區請求,已添加到 .NET 6 中

此更改增加了對(duì) Socks4、Socks4a 和 Socks5 的支持。例如,它允許通過(guò) SSH 測試外部連接或連接到 Tor 網絡

該WebProxy班現在接受socks的方案,你可以在下面(miàn)的例子中看到。

var handler = new HttpClientHandler
{
    Proxy = new WebProxy("socks5://127.0.0.1", 9050)
};
var httpClient = new HttpClient(handler);

感謝霍耀元

Microsoft.Extensions.Hosting — ConfigureHostOptions API

我們在 IHostBuilder 上添加了一個新的 ConfigureHostOptions API 以簡化應用程序設置(例如,配置關閉超時(shí)):

using HostBuilder host = new()
    .ConfigureHostOptions(o =>
    {
        o.ShutdownTimeout = TimeSpan.FromMinutes(10);
    })
    .Build();

host.Run();

在 .NET 5 中,配置主機選項有點複雜:

using HostBuilder host = new()
    .ConfigureServices(services =>
    {
        services.Configure(o =>
        {
            o.ShutdownTimeout = TimeSpan.FromMinutes(10);
        });
    })
    .Build();

host.Run();

Microsoft.Extensions.DependencyInjection — CreateAsyncScope API

CreateAsyncScopeAPI是爲了處理處置IAsyncDisposable服務。以前,您可能(néng)已經(jīng)注意到,對(duì)IAsyncDisposable服務提供者的處置可能(néng)會(huì)引發(fā)InvalidOperationException異常。

下面(miàn)的示例演示了新模式,CreateAsyncScope用于啓用using語句的安全使用。

await using (var scope = provider.CreateAsyncScope())
{
    var foo = scope.ServiceProvider.GetRequiredService();
}

下面(miàn)的例子演示了現有的問題案例:

using System;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;

await using var provider = new ServiceCollection()
        .AddScoped()
        .BuildServiceProvider();

// This using can throw InvalidOperationException
using (var scope = provider.CreateScope())
{
    var foo = scope.ServiceProvider.GetRequiredService();
}

class Foo : IAsyncDisposable
{
    public ValueTask DisposeAsync() => default;
}

以下模式是之前建議的避免異常的解決方法。不再需要它。

var scope = provider.CreateScope();
var foo = scope.ServiceProvider.GetRequiredService();
await ((IAsyncDisposable)scope).DisposeAsync();

感謝馬丁Björkström

Microsoft.Extensions.Logging — 編譯時(shí)源代碼生成(chéng)器

.NET 6href="https://github.com/dotnet/runtime/issues/52549">引入了LoggerMessageAttribute類型. 此屬性是命名空間的一部分,使用時(shí),它會(huì)源生成(chéng)高性能(néng)日志 API。源代碼生成(chéng)日志支持旨在爲現代 .NET 應用程序提供高度可用和高性能(néng)的日志解決方案。自動生成(chéng)的源代碼依賴于接口和功能(néng)。Microsoft.Extensions.LoggingILoggerLoggerMessage.Define

當LoggerMessageAttribute用于partial日志記錄方法時(shí)觸發(fā)源生成(chéng)器。當被觸發(fā)時(shí),它要麼(me)能(néng)夠自動生成(chéng)partial它正在裝飾的方法的實現,要麼(me)生成(chéng)帶有正确使用提示的編譯時(shí)診斷。編譯時(shí)日志記錄解決方案在運行時(shí)通常比現有日志記錄方法快得多。它通過(guò)最大限度地消除裝箱、臨時(shí)分配和副本來實現這(zhè)一點。

與直接手動使用API 相比,有以下好(hǎo)處:LoggerMessage.Define

  • 更短更簡單的語法:聲明性屬性使用而不是編碼樣闆。

  • 引導開(kāi)發(fā)者體驗:生成(chéng)器給出警告,幫助開(kāi)發(fā)者做正确的事(shì)。

  • 支持任意數量的日志參數。最多支持六個。LoggerMessage.Define

  • 支持動态日志級别。這(zhè)是不可能(néng)的。LoggerMessage.Define

要使用LoggerMessageAttribute,消費類和方法需要是partial。代碼生成(chéng)器在編譯時(shí)觸發(fā)并生成(chéng)該partial方法的實現。

public static partial class Log
{
    [LoggerMessage(EventId = 0, Level = LogLevel.Critical, Message = "Could not open socket to `{hostName}`")]
    public static partial void CouldNotOpenSocket(ILogger logger, string hostName);
}

在前面(miàn)的示例中,日志記錄方法是,static并且在屬性定義中指定了日志級别。在靜态上下文中使用屬性時(shí),ILogger需要實例作爲參數。您也可以選擇在非靜态上下文中使用該屬性。有關更多示例和使用場景,請訪問編譯時(shí)日志源生成(chéng)器文檔。

System.Linq — 可枚舉支持Index和Range參數

該方法現在接受可枚舉項末尾的索引,如下例所示。Enumerable.ElementAt

Enumerable.Range(1, 10).ElementAt(^2); // returns 9

添加了一個接受參數的重載。它簡化了對(duì)可枚舉序列的切片:Enumerable.TakeRange

  • source.Take(..3) 代替 source.Take(3)

  • source.Take(3..) 代替 source.Skip(3)

  • source.Take(2..7) 代替 source.Take(7).Skip(2)

  • source.Take(^3..) 代替 source.TakeLast(3)

  • source.Take(..^3) 代替 source.SkipLast(3)

  • source.Take(^7..^3)而不是.source.TakeLast(7).SkipLast(3)

感謝@dixin

System.Linq — TryGetNonEnumeratedCount

該TryGetNonEnumeratedCount方法嘗試在不強制枚舉的情況下獲取源可枚舉的計數。這(zhè)種(zhǒng)方法在枚舉之前預分配緩沖區很有用的情況下很有用,如下例所示。

List buffer = source.TryGetNonEnumeratedCount(out int count) ? new List(capacity: count) : new List();
foreach (T item in source)
{
    buffer.Add(item);
}

TryGetNonEnumeratedCount檢查實現ICollection/或利用Linq 使用的一些内部優化的源。ICollection

System.Linq — DistinctBy/ UnionBy/ IntersectBy/ExceptBy

新的變體已添加到集合操作中,允許使用鍵選擇器函數指定相等性,如下面(miàn)的示例所示。

Enumerable.Range(1, 20).DistinctBy(x => x % 3); // {1, 2, 3}

var first = new (string Name, int Age)[] { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) };
var second = new (string Name, int Age)[] { ("Claire", 30), ("Pat", 30), ("Drew", 33) };
first.UnionBy(second, person => person.Age); // { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40), ("Drew", 33) }

System.Linq — MaxBy/MinBy

MaxBy和MinBy方法允許使用鍵選擇器查找最大或最小元素,如下例所示。

var people = new (string Name, int Age)[] { ("Francis", 20), ("Lindsey", 30), ("Ashley", 40) };
people.MaxBy(person => person.Age); // ("Ashley", 40)

System.Linq — Chunk

Chunk 可用于將(jiāng)可枚舉的源分塊爲固定大小的切片,如下例所示。

IEnumerable chunks = Enumerable.Range(0, 10).Chunk(size: 3); // { {0,1,2}, {3,4,5}, {6,7,8}, {9} }

歸功于羅伯特·安德森

System.Linq的- FirstOrDefault/ LastOrDefault/SingleOrDefault過(guò)載采取默認參數

現有FirstOrDefault/ LastOrDefault/SingleOrDefault方法返回如果源枚舉是空的。添加了新的重載,接受在這(zhè)種(zhǒng)情況下要返回的默認參數,如下面(miàn)的示例所示。default(T)

Enumerable.Empty().SingleOrDefault(-1); // returns -1

感謝@ Foxtrek64

System.Linq —Zip接受三個枚舉的重載

郵編方法現在支持組合三個枚舉接口,你可以在下面(miàn)的例子中看到。

var xs = Enumerable.Range(1, 10);
var ys = xs.Select(x => x.ToString());
var zs = xs.Select(x => x % 2 == 0);

foreach ((int x, string y, bool z) in Enumerable.Zip(xs,ys,zs))
{
}

感謝霍耀元

優先隊列

PriorityQueue(System.Collections.Generic) 是一個新集合,可以添加具有值和優先級的新項目。在出隊時(shí),PriorityQueue 返回具有最低優先級值的元素。您可以將(jiāng)這(zhè)個新集合視爲類似于但每個入隊元素都(dōu)有一個影響出隊行爲的優先級值。Queue

以下示例演示了.PriorityQueue

// creates a priority queue of strings with integer priorities
var pq = new PriorityQueue();

// enqueue elements with associated priorities
pq.Enqueue("A", 3);
pq.Enqueue("B", 1);
pq.Enqueue("C", 2);
pq.Enqueue("D", 3);

pq.Dequeue(); // returns "B"
pq.Dequeue(); // returns "C"
pq.Dequeue(); // either "A" or "D", stability is not guaranteed.

感謝Patryk Golebiowski

更快地將(jiāng)結構體作爲字典值處理

CollectionsMarshal.GetValueRef是一個新的不安全API,它可以更快地更新字典中的結構值。新 API 旨在用于高性能(néng)場景,而不是用于一般用途。它返回refstruct 值,然後(hòu)可以使用典型技術就(jiù)地更新。

以下示例演示了如何使用新 API:

ref MyStruct value = CollectionsMarshal.GetValueRef(dictionary, key);
// Returns Unsafe.NullRef() if it doesn't exist; check using Unsafe.IsNullRef(ref value)
if (!Unsafe.IsNullRef(ref value))
{
    // Mutate in-place
    value.MyInt++;
}

在此更改之前,更新struct字典值對(duì)于高性能(néng)場景可能(néng)會(huì)很昂貴,需要字典查找和struct. 然後(hòu)在更改 之後(hòu)struct,它將(jiāng)再次分配給字典鍵,從而導緻另一次查找和複制操作。此改進(jìn)將(jiāng)密鑰散列減少到 1(從 2)并删除所有結構複制操作。

歸功于本·亞當斯

新的DateOnly和TimeOnly結構

添加了僅限日期和時(shí)間的結構,具有以下特征:

  • 每個代表 a 的一半DateTime,或者隻是日期部分,或者隻是時(shí)間部分。

  • DateOnly是生日、周年紀念日和工作日的理想選擇。它符合 SQL Server 的date類型。

  • TimeOnly是定期會(huì)議、鬧鍾和每周工作時(shí)間的理想選擇。它符合 SQL Server 的time類型。

  • 補充現有的日期/時(shí)間類型 ( DateTime, DateTimeOffset, TimeSpan, TimeZoneInfo)。

  • 在System命名空間中,在 CoreLib 中提供,就(jiù)像現有的相關類型一樣。

性能(néng)改進(jìn) DateTime.UtcNow

這(zhè)種(zhǒng)改進(jìn)有以下好(hǎo)處:

  • 修複了在 Windows 上獲取系統時(shí)間的2.5 倍性能(néng)回歸

  • 利用 5 分鍾的 Windows 閏秒數據滑動緩存,而不是在每次調用時(shí)獲取。

支持所有平台上的 Windows 和 IANA 時(shí)區

這(zhè)種(zhǒng)改進(jìn)有以下好(hǎo)處:

  • 使用時(shí)的隐式轉換(https://github.com/dotnet/runtime/pull/49412)TimeZoneInfo.FindSystemTimeZoneById

  • 通過(guò)新的API顯式轉換上TimeZoneInfo:TryConvertIanaIdToWindowsId,TryConvertWindowsIdToIanaId,和HasIanaId(https://github.com/dotnet/runtime/issues/49407

  • 改進(jìn)了使用不同時(shí)區類型的系統之間的跨平台支持和互操作。

  • 删除需要使用 TimeZoneConverter OSS 庫。該功能(néng)現已内置。

改進(jìn)的時(shí)區顯示名稱

Unix 上的時(shí)區顯示名稱已得到改進(jìn)

  • 從 返回的列表中的顯示名稱中消除歧義。TimeZoneInfo.GetSystemTimeZones

  • 利用 ICU / CLDR 全球化數據。

  • 僅适用于 Unix。Windows 仍然使用注冊表數據。這(zhè)可能(néng)會(huì)在以後(hòu)更改。

還(hái)進(jìn)行了以下額外改進(jìn):

  • UTC 時(shí)區的顯示名稱和标準名稱被硬編碼爲英語,現在使用與其餘時(shí)區數據相同的語言(CurrentUICulture在 Unix 上,Windows 上的操作系統默認語言)。

  • 由于大小限制,Wasm 中的時(shí)區顯示名稱改爲使用非本地化的 IANA ID。

  • TimeZoneInfo.AdjustmentRule嵌套類將(jiāng)其BaseUtcOffsetDelta内部屬性公開(kāi)并獲得一個新的構造函數baseUtcOffsetDelta作爲參數。(https://github.com/dotnet/runtime/issues/50256

  • TimeZoneInfo.AdjustmentRule 還(hái)獲得了在 Unix 上加載時(shí)區的其他修複(https://github.com/dotnet/runtime/pull/49733),(https://github.com/dotnet/runtime/pull/50131

改進(jìn)了對(duì) Windows ACL 的支持

System.Threading.AccessControl現在包括對(duì)與 Windows 訪問控制列表 (ACL) 交互的改進(jìn)支持。爲、和的OpenExisting和TryOpenExisting方法添加了新的重載。這(zhè)些具有“安全權限”實例的重載允許打開(kāi)使用特殊 Windows 安全屬性創建的線程同步對(duì)象的現有實例。EventWaitHandleMutexSemaphore

此更新與 .NET Framework 中可用的 API 相匹配,并且具有相同的行爲。

以下示例演示如何使用這(zhè)些新 API。

對(duì)于Mutex:

var rights = MutexRights.FullControl;
string mutexName = "MyMutexName";

var security = new MutexSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
MutexAccessRule accessRule = new MutexAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);

// createdMutex, openedMutex1 and openedMutex2 point to the same mutex
Mutex createdMutex = MutexAcl.Create(initiallyOwned: true, mutexName, out bool createdNew, security);
Mutex openedMutex1 = MutexAcl.OpenExisting(mutexName, rights);
MutexAcl.TryOpenExisting(mutexName, rights, out Mutex openedMutex2);

爲了 Semaphore

var rights = SemaphoreRights.FullControl;
string semaphoreName = "MySemaphoreName";

var security = new SemaphoreSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
SemaphoreAccessRule accessRule = new SemaphoreAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);

// createdSemaphore, openedSemaphore1 and openedSemaphore2 point to the same semaphore
Semaphore createdSemaphore = SemaphoreAcl.Create(initialCount: 1,  maximumCount: 3, semaphoreName, out bool createdNew, security);
Semaphore openedSemaphore1 = SemaphoreAcl.OpenExisting(semaphoreName, rights);
SemaphoreAcl.TryOpenExisting(semaphoreName, rights, out Semaphore openedSemaphore2);

爲了 EventWaitHandle

var rights = EventWaitHandleRights.FullControl;
string eventWaitHandleName = "MyEventWaitHandleName";

var security = new EventWaitHandleSecurity();
SecurityIdentifier identity = new SecurityIdentifier(WellKnownSidType.BuiltinUsersSid, null);
EventWaitHandleAccessRule accessRule = new EventWaitHandleAccessRule(identity, rights, AccessControlType.Allow);
security.AddAccessRule(accessRule);

// createdHandle, openedHandle1 and openedHandle2 point to the same event wait handle
EventWaitHandle createdHandle = EventWaitHandleAcl.Create(initialState: true, EventResetMode.AutoReset, eventWaitHandleName, out bool createdNew, security);
EventWaitHandle openedHandle1 = EventWaitHandleAcl.OpenExisting(eventWaitHandleName, rights);
EventWaitHandleAcl.TryOpenExisting(eventWaitHandleName, rights, out EventWaitHandle openedHandle2);

HMAC 一次性方法

HMAC類現在有靜态方法,允許HMACs的一次性計算不分配。這(zhè)些添加類似于先前版本中添加的用于哈希生成(chéng)的一次性方法。System.Security.Cryptography

DependentHandle 現在是公開(kāi)的

該DependentHandle類型現在是公開(kāi)的,具有以下 API 表面(miàn)

namespace System.Runtime
{
    public struct DependentHandle : IDisposable
    {
        public DependentHandle(object? target, object? dependent);
        public bool IsAllocated { get; }
        public object? Target { get; set; }
        public object? Dependent { get; set; }
        public (object? Target, object? Dependent) TargetAndDependent { get; }
        public void Dispose();
    }
}

它可用于創建高級系統,例如複雜的緩存系統或該類型的自定義版本。例如,MVVM 工具包中的類型將(jiāng)使用它來避免廣播消息時(shí)的内存分配。ConditionalWeakTableWeakReferenceMessenger

可移植線程池

.NET線程池已重新實現作爲一個托管實現,現在作爲默認的線程池.NET 6.我們做出這(zhè)一改變,使所有的.NET應用程序能(néng)夠訪問線程池獨立的是否相同正在使用 CoreCLR、Mono 或任何其他運行時(shí)。作爲此更改的一部分,我們沒(méi)有觀察到或預期任何功能(néng)或性能(néng)影響。

RyuJIT

該團隊對(duì)該版本的 .NET JIT 編譯器進(jìn)行了許多改進(jìn),在每個預覽帖子中都(dōu)有記錄。大多數更改都(dōu)可以提高性能(néng)。這(zhè)裡(lǐ)涵蓋了一些 RyuJIT 的亮點。

動态 PGO

在 .NET 6 中,我們啓用了兩(liǎng)種(zhǒng)形式的 PGO(配置文件引導優化):

  • 動态 PGO使用從當前運行收集的數據來優化當前運行。

  • 靜态 PGO依靠從過(guò)去運行中收集的數據來優化未來運行。

動态 PGO 已經(jīng)在文章前面(miàn)的性能(néng)部分中介紹過(guò)。我會(huì)提供一個重新上限。

動态 PGO 使 JIT 能(néng)夠在運行時(shí)收集有關實際用于該特定應用程序運行的代碼路徑和類型的信息。然後(hòu) JIT 可以根據這(zhè)些代碼路徑優化代碼,有時(shí)會(huì)顯著(zhe)提高性能(néng)。我們在測試和生産方面(miàn)都(dōu)看到了健康的兩(liǎng)位數改進(jìn)。有一組經(jīng)典的編譯器技術,在沒(méi)有 PGO 的情況下使用 JIT 或提前編譯是不可能(néng)的。我們現在能(néng)夠應用這(zhè)些技術。熱/冷分裂是一種(zhǒng)這(zhè)樣的技術,去虛拟化是另一種(zhǒng)技術。

要啓用動态 PGO,請在您的應用程序將(jiāng)運行的環境中進(jìn)行設置。DOTNET_TieredPGO=1

如性能(néng)部分所述,動态 PGO 爲 TechEmpower JSON“MVC”套件的每秒請求數提供了 26% 的改進(jìn)(510K -> 640K)。這(zhè)是一個驚人的改進(jìn),無需更改代碼。

我們的目标是在 .NET 的未來版本中默認啓用 Dynamic PGO,希望與 .NET 7 一起(qǐ)使用。我們強烈建議您在您的應用程序中嘗試使用 Dynamic PGO 并向(xiàng)我們提供反饋。

完整的 PGO

要獲得動态 PGO 的全部優勢,您可以設置兩(liǎng)個額外的環境變量:和. 這(zhè)确保了盡可能(néng)多的方法參與分層編譯。我們稱這(zhè)種(zhǒng)變體爲Full PGO。與動态 PGO 相比,完整 PGO 可以提供更大的穩态性能(néng)優勢,但啓動時(shí)間會(huì)更慢(因爲必須在第 0 層執行更多方法)。DOTNET_TC_QuickJitForLoops=1DOTNET_ReadyToRun=0

您不希望將(jiāng)此選項用于短期運行的無服務器應用程序,但對(duì)于長(cháng)期運行的應用程序可能(néng)有意義。

在未來的版本中,我們計劃精簡和簡化這(zhè)些選項,以便您可以更簡單地獲得完整 PGO 的好(hǎo)處,并适用于更廣泛的應用程序。

靜态 PGO

我們目前使用靜态 PGO來優化 .NET 庫程序集,如随 R2R(準備運行)提供的程序集。System.Private.CoreLib

靜态 PGO 的好(hǎo)處在于,當程序集使用 crossgen 編譯爲 R2R 格式時(shí),會(huì)進(jìn)行優化。這(zhè)意味著(zhe)在沒(méi)有運行時(shí)成(chéng)本的情況下有運行時(shí)優勢。例如,這(zhè)非常重要,這(zhè)也是 PGO 對(duì) C++ 很重要的原因。

循環對(duì)齊

内存對(duì)齊是現代計算中各種(zhǒng)操作的常見要求。在 .NET 5 中,我們開(kāi)始在 32 字節邊界對(duì)齊方法。在 .NET 6 中,我們添加了一個功能(néng)來執行自适應循環對(duì)齊,該功能(néng)NOP在具有循環的方法中添加填充指令,以便循環代碼從 mod(16) 或 mod(32) 内存地址開(kāi)始。這(zhè)些更改提高并穩定了 .NET 代碼的性能(néng)。

在下面(miàn)的冒泡排序圖中,數據點 1 表示我們開(kāi)始在 32 字節邊界對(duì)齊方法的點。數據點 2 表示我們也開(kāi)始對(duì)齊内部循環的點。如您所見,基準測試的性能(néng)和穩定性都(dōu)有顯著(zhe)提高。





硬件加速結構

結構是 CLR 類型系統的重要組成(chéng)部分。近年來,它們經(jīng)常被用作整個 .NET 庫中的性能(néng)原語。最近的例子是ValueTask,ValueTuple和。記錄結構是一個新的例子。在 .NET 5 和 .NET 6 中,我們一直在提高結構的性能(néng),部分是通過(guò)确保結構可以保存在超快的 CPU 寄存器中,當它們是局部變量、參數或方法的返回值時(shí))。這(zhè)對(duì)于使用向(xiàng)量計算的 API 尤其有益。Span

穩定性能(néng)測量

團隊中有大量的工程系統工作從未出現在博客上。這(zhè)适用于您使用的任何硬件或軟件産品。JIT 團隊開(kāi)展了一個項目來穩定性能(néng)測量,目标是增加我們内部性能(néng)實驗室自動化自動報告的回歸值。這(zhè)個項目很有趣,因爲進(jìn)行了深入的調查以及實現穩定性所需的産品更改。它還(hái)展示了我們衡量保持和提高績效的規模。





此圖像展示了不穩定的性能(néng)測量,其中性能(néng)在連續運行中在慢速和快速之間波動。x 軸是測試日期,y 軸是測試時(shí)間(以納秒爲單位)。在圖表的末尾(在提交這(zhè)些更改之後(hòu)),您可以看到測量值穩定下來,結果最好(hǎo)。此圖像演示了單個測試。還(hái)有更多測試在dotnet/runtime #43227中被證明具有類似的行爲。

準備運行的代碼 / Crossgen 2

Crossgen2 是crossgen 工具的替代品。它旨在滿足兩(liǎng)個結果:

  • 使跨代開(kāi)發(fā)更高效。

  • 啓用一組當前無法通過(guò) crossgen 實現的功能(néng)。

這(zhè)種(zhǒng)轉換有點類似于本機代碼 csc.exe 到托管代碼Roslyn 編譯器。Crossgen2 是用 C# 編寫的,但是,它沒(méi)有像 Roslyn 那樣公開(kāi)花哨的 API。

我們可能(néng)已經(jīng)/已經(jīng)爲 .NET 6 和 7 計劃了六個依賴于 crossgen2 的項目。矢量指令默認值提議是我們想要爲 .NET 6 進(jìn)行的 crossgen2 功能(néng)和産品更改的一個很好(hǎo)的例子,但更有可能(néng)是 .NET 7。版本氣泡是另一個很好(hǎo)的例子。

Crossgen2 支持跨操作系統和架構維度的交叉編譯(因此得名“crossgen”)。這(zhè)意味著(zhe)您將(jiāng)能(néng)夠使用單個構建機器爲所有目标生成(chéng)本機代碼,至少與準備運行的代碼相關。然而,運行和測試該代碼是另一回事(shì),爲此您需要适當的硬件和操作系統。

第一步是用crossgen2編譯平台本身。我們使用 .NET 6 完成(chéng)了所有架構。因此,我們能(néng)夠在此版本中淘汰舊的 crossgen。請注意,crossgen2 僅适用于 CoreCLR,不适用于基于 Mono 的應用程序(它們具有一組單獨的代碼生成(chéng)工具)。

這(zhè)個項目——至少在開(kāi)始時(shí)——并不以性能(néng)爲導向(xiàng)。目标是爲托管 RyuJIT(或任何其他)編譯器提供更好(hǎo)的架構,以離線方式(不需要或啓動運行時(shí))生成(chéng)代碼。

您可能(néng)會(huì)說(shuō)“嘿……如果 crossgen2 是用 C# 編寫的,那麼(me)您不必啓動運行時(shí)來運行它嗎?” 是的,但這(zhè)不是本文中“離線”的意思。當 crossgen2 運行時(shí),我們沒(méi)有使用 crossgen2 運行時(shí)附帶的 JIT 來生成(chéng)準備運行 (R2R) 代碼. 那是行不通的,至少對(duì)于我們的目标是行不通的。想象 crossgen2 在 x64 機器上運行,我們需要爲 Arm64 生成(chéng)代碼。Crossgen2 加載 Arm64 RyuJIT(爲 x64 編譯)作爲本機插件,然後(hòu)使用它生成(chéng) Arm64 R2R 代碼。機器指令隻是保存到文件中的字節流。它也可以反方向(xiàng)工作。在 Arm64 上,crossgen2 可以使用編譯爲 Arm64 的 x64 RyuJIT 生成(chéng) x64 代碼。我們使用相同的方法在 x64 機器上定位 x64 代碼。Crossgen2 加載爲任何需要的配置構建的 RyuJIT。這(zhè)可能(néng)看起(qǐ)來很複雜,但如果您想啓用無縫的交叉目标模型,它就(jiù)是您需要的那種(zhǒng)系統,而這(zhè)正是我們想要的。

我們希望隻在一個版本中使用“crossgen2”這(zhè)個術語,之後(hòu)它將(jiāng)取代現有的 crossgen,然後(hòu)我們將(jiāng)回到對(duì)“crossgen2”使用“crossgen”這(zhè)個術語。

.NET 診斷:EventPipe

EventPipe 是我們的跨平台機制,用于在進(jìn)程内或進(jìn)程外輸出事(shì)件、性能(néng)數據和計數器。從 .NET 6 開(kāi)始,我們已將(jiāng)實現從 C++ 移至 C。通過(guò)此更改,Mono 也使用 EventPipe。這(zhè)意味著(zhe) CoreCLR 和 Mono 使用相同的事(shì)件基礎結構,包括 .NET 診斷 CLI 工具。

這(zhè)一變化還(hái)伴随著(zhe) CoreCLR 的小幅縮小:


我們還(hái)進(jìn)行了一些更改,以在負載下提高 EventPipe 吞吐量。在最初的幾個預覽版中,我們進(jìn)行了一系列更改,使吞吐量提高了 .NET 5 所能(néng)達到的 2.06 倍:





對(duì)于此基準測試,越高越好(hǎo)。.NET 6 是橙色線,.NET 5 是藍色線。

開(kāi)發(fā)工具包

對(duì) .NET SDK 進(jìn)行了以下改進(jìn)。

.NET 6 SDK 可選工作負載的 CLI 安裝

.NET 6 引入了SDK 工作負載的概念。工作負載是可選組件,可以安裝在 .NET SDK 之上以啓用各種(zhǒng)方案。.NET 6 中的新工作負載是:.NET MAUI 和 Blazor WebAssembly AOT 工作負載。我們可能(néng)會(huì)在 .NET 7 中創建新的工作負載(可能(néng)來自現有的 SDK)。工作負載的最大好(hǎo)處是尺寸減小和可選性。我們希望随著(zhe)時(shí)間的推移使 SDK 變得更小,并允許隻安裝您需要的組件。這(zhè)個模型對(duì)開(kāi)發(fā)者機器有好(hǎo)處,甚至對(duì) CI 更好(hǎo)。

Visual Studio 用戶實際上不需要擔心工作負載。工作負載功能(néng)的設計目的是讓安裝協調器(如 Visual Studio)可以爲您安裝工作負載。可以通過(guò) CLI 直接管理工作負載。

工作負載功能(néng)公開(kāi)了多個用于管理工作負載的動詞,包括以下内容:

  • dotnet workload restore — 安裝給定項目所需的工作負載。

  • dotnet workload install — 安裝命名的工作負載。

  • dotnet workload list — 列出您已安裝的工作負載。

  • dotnet workload update — 將(jiāng)所有已安裝的工作負載更新到最新的可用版本。

該update動詞查詢更新的工作負載清單、更新本地清單、下載已安裝工作負載的新版本,然後(hòu)删除工作負載的所有舊版本。這(zhè)類似于(在基于 Debian 的 Linux 發(fā)行版上使用)。將(jiāng)工作負載視爲 SDK 的私有包管理器是合理的。它是私有的,因爲它僅可用于 SDK 組件。我們將(jiāng)來可能(néng)會(huì)重新考慮這(zhè)一點。nuget.orgapt update && apt upgrade -y

這(zhè)些dotnet workload命令在給定 SDK 的上下文中運行。假設您同時(shí)安裝了 .NET 6 和 .NET 7。工作負載命令將(jiāng)爲每個 SDK 提供不同的結果,因爲工作負載會(huì)有所不同(至少相同工作負載的不同版本)。

請注意,dotnet workload install將(jiāng)工作負載從 NuGet.org 複制到您的 SDK 安裝中,因此sudo如果 SDK 安裝位置受到保護(意味著(zhe)在管理員/根位置),則需要提升運行或使用。

内置SDK版本檢查

爲了更輕松地跟蹤新版本的 SDK 和運行時(shí)何時(shí)可用,我們向(xiàng) .NET 6 SDK 添加了一個新命令。

dotnet sdk check

它會(huì)告訴您是否有更新版本可用于您已安裝的任何 .NET SDK、運行時(shí)或工作負載。您可以在下圖中看到新體驗。





dotnet new

您現在可以在http://NuGet.org中搜索帶有.dotnet new --search

模闆安裝的其他改進(jìn)包括支持開(kāi)關以支持私有 NuGet 源的授權憑據。--interactive

安裝 CLI 模闆後(hòu),您可以通過(guò)和檢查更新是否可用。--update-check--update-apply

NuGet 包驗證

包驗證工具使 NuGet 庫開(kāi)發(fā)人員能(néng)夠驗證他們的包是否一緻且格式良好(hǎo)。

這(zhè)包括:

  • 驗證跨版本沒(méi)有重大更改。

  • 驗證包對(duì)于所有特定于運行時(shí)的實現是否具有相同的公共 API 集。

  • 确定任何目标框架或運行時(shí)适用性差距。

此工具是 SDK 的一部分。使用它的最簡單方法是在項目文件中設置一個新屬性。

 true

更多 Roslyn 分析儀

在 .NET 5 中,我們随 .NET SDK 提供了大約 250 個分析器。其中許多已經(jīng)存在,但作爲 NuGet 包在帶外發(fā)布。我們爲 .NET 6 添加了更多分析器

默認情況下,大多數新分析器在信息級别啓用。您可以通過(guò)啓用這(zhè)些分析儀在警告級别配置的分析模式是這(zhè)樣的:。All

我們發(fā)布了我們想要的 .NET 6 分析器集(加上一些額外的東西),然後(hòu)將(jiāng)其中的大部分都(dōu)準備好(hǎo)了。社區添加了幾個實現,包括這(zhè)些。

感謝Meik TranelNewell Clark

爲平台兼容性分析器啓用自定義防護

該CA1416平台兼容性分析儀已經(jīng)可以識别使用的方法平台警衛OperatingSystem和RuntimeInformation,如和。但是,分析器不識别任何其他保護可能(néng)性,例如緩存在字段或屬性中的平台檢查結果,或者在輔助方法中定義了複雜的平台檢查邏輯。OperatingSystem.IsWindowsOperatingSystem.IsWindowsVersionAtLeast

爲了允許自定義保護的可能(néng)性,我們添加了新屬性SupportedOSPlatformGuard并UnsupportedOSPlatformGuard使用相應的平台名稱和/或版本注釋自定義保護成(chéng)員。該注釋被平台兼容性分析器的流分析邏輯識别和尊重。

用法

    [UnsupportedOSPlatformGuard("browser")] // The platform guard attribute
#if TARGET_BROWSER
    internal bool IsSupported => false;
#else
    internal bool IsSupported => true;
#endif

    [UnsupportedOSPlatform("browser")]
    void ApiNotSupportedOnBrowser() { }

    void M1()
    {
        ApiNotSupportedOnBrowser();  // Warns: This call site is reachable on all platforms.'ApiNotSupportedOnBrowser()' is unsupported on: 'browser'

        if (IsSupported)
        {
            ApiNotSupportedOnBrowser();  // Not warn
        }
    }

    [SupportedOSPlatform("Windows")]
    [SupportedOSPlatform("Linux")]
    void ApiOnlyWorkOnWindowsLinux() { }

    [SupportedOSPlatformGuard("Linux")]
    [SupportedOSPlatformGuard("Windows")]
    private readonly bool _isWindowOrLinux = OperatingSystem.IsLinux() || OperatingSystem.IsWindows();

    void M2()
    {
        ApiOnlyWorkOnWindowsLinux();  // This call site is reachable on all platforms.'ApiOnlyWorkOnWindowsLinux()' is only supported on: 'Linux', 'Windows'.

        if (_isWindowOrLinux)
        {
            ApiOnlyWorkOnWindowsLinux();  // Not warn
        }
    }
}

最後(hòu)

歡迎使用 .NET 6。它是另一個巨大的 .NET 版本,在性能(néng)、功能(néng)、可用性和安全性方面(miàn)的改進(jìn)幾乎相同。我們希望您找到許多改進(jìn),最終使您在日常開(kāi)發(fā)中更有效率和能(néng)力,并提高性能(néng)或降低生産應用程序的成(chéng)本。我們已經(jīng)開(kāi)始聽到你們中那些已經(jīng)開(kāi)始使用 .NET 6 的人的好(hǎo)消息。

在 Microsoft,我們也處于 .NET 6 部署的早期階段,一些關鍵應用程序已經(jīng)投入生産,未來幾周和幾個月内還(hái)有更多應用程序即將(jiāng)推出。

.NET 6 是我們最新的 LTS 版本。我們鼓勵所有人轉向(xiàng)它,特别是如果您使用 .NET 5。我們期待它成(chéng)爲有史以來采用速度最快的 .NET 版本。

此版本是至少 1000 人(但可能(néng)更多)的結果。這(zhè)包括來自 Microsoft 的 .NET 團隊以及社區中的更多人。我試圖在這(zhè)篇文章中包含許多社區貢獻的功能(néng)。感謝您花時(shí)間創建這(zhè)些并完成(chéng)我們的流程。我希望這(zhè)次經(jīng)曆是好(hǎo)的,更多的人會(huì)做出貢獻。

這(zhè)篇文章是許多有才華的人合作的結果。這(zhè)些貢獻包括團隊在整個發(fā)布過(guò)程中提供的功能(néng)内容、爲此最終帖子創建的重要新内容,以及使最終内容達到您應得的質量所需的大量技術和散文更正。很高興爲您制作它和所有其他帖子。

感謝您成(chéng)爲 .NET 開(kāi)發(fā)人員。


來源:知乎