`.NET 11 Preview 2 发布了。如果你以为这只是又一个多加了几个 API 的预览版,那我建议你把它当回事——从架构演进的视角看,这版 Preview 透出的方向,比新增功能本身有价值得多。这篇文章我会从版本节奏、架构主线、核心实现细节、升级实测和避坑经验五个方面,把 .NET 11 Preview 2 真正值得看的东西拆开讲清楚。无论你是还在观望的架构师,还是已经在 .NET 8/9 上跑生产应用的开发者,又或是打算把服务原生编译后部署上容器的运维同学,应该都能从这里拿到自己能用的东西。
1. Preview 2 不是简单更新:先理清版本节奏和定位
1.1 .NET 为什么年年发新版本,LTS 和 STS 怎么选
.NET 从很多年前开始就固定了年度发布节奏:每年 11 月左右出一个大版本,偶数版本是 LTS(长期支持),奇数版本是 STS(标准期限支持)。.NET 8 是 LTS,.NET 9 是 STS,.NET 10 是 LTS,而 .NET 11 作为 2026 年 11 月 GA 的版本,定位上依然是 STS。这意味着它会有 18 个月的支持周期,适合追新、愿意持续升级的团队,而不太适合那种“部署完三年不管”的场景。
Preview 1 通常在一个版本周期的早期放出,主要目的是定方向和收集反馈;Preview 2 则是在 Preview 1 基础上的第一次实质迭代。到了 Preview 2 这个节点,编译器、运行时、类库的改动已经开始成形,API 提案也从“想法”进入“原型”状态。当然它依然不是稳定的,任何功能都可能在下个 Preview 或 RC 阶段被砍掉、改名、扭转行为。所以你现在看到的东西,不能直接拿去做生产评估,但完全可以拿来做技术预研和架构沙盘演练。
1.2 Preview 2 这个阶段能看到什么有效信息
预览版的价值不在于“它已经能干什么”,而在于“它准备往哪里走”。你可以在 Preview 2 阶段重点关注四类信号:
- 运行时层面的 PR 和 Issue:GC 的调整、JIT 的优化、线程池的启发式改动,这些在代码提交记录里能看到比 release notes 更细的线索。
- 类库 API 提案:哪些接口被标记为 obsolete、哪些抽象被合并、哪些新抽象出现,这直接关系到你现有代码未来是否要返工。
- 工具链变化:SDK 自身的启动速度、构建性能、发布命令的参数调整,会直接影响 CI/CD 流水线。
- 破坏性变更预告:每个大版本都会有一批 breaking changes,Preview 2 阶段通常会在文档里列出大部分有争议的项,提前看到就能提前规划迁移成本。
我见过很多团队忽略这个阶段,直接等 RC 甚至 GA 才动手,结果发现自己依赖的某个库在新运行时下行为完全不同,最后不得不加班改代码。预览版的价值恰恰在于“提前暴露问题”,而不是“提前享受新功能”。
2. 架构演进主线:AOT、运行时精简与云原生这三个方向
2.1 从“性能优化”走向“性能工程”,内置基准成为标配
这几年 .NET 在新版本上反复强调性能,但 Preview 2 给我的感觉是,团队对性能这件事的态度已经从“单项优化”变成了“工程化治理”。什么意思?以前每个版本会宣传“某某方法快了百分之多少”,这是单点优化;现在他们把性能基准直接嵌进 PR 流程,每个改动都要跑基准测试,防止性能回归合并进主干。
这对普通开发者最直接的影响是:你在升级 .NET 11 后,同样一份代码大概率会更快,但这个“快”不是靠某个魔法开关,而是靠一大批细微改进叠加出来的。比如 JIT 的内联策略调整、循环优化加强、栈上替换(OSR)更成熟,以及异常路径的冷热分离。单看每一项可能只有个位数百分比的提升,合在一起在高并发服务上就是可感知的改善。
另外,Preview 2 延续了把诊断工具做得更易用的思路。dotnet-counters、dotnet-trace、dotnet-dump 这些工具本身不是新东西,但新版本会继续完善对 CPU 采样、内存分配的展示粒度,让开发者在压测时更快定位到热点代码。我建议你从这个版本开始把 OpenTelemetry 的 metrics 和 traces 纳入日常开发依赖,别等上了生产再补可观测性。
2.2 AOT 与 NativeAOT 的持续深化,动态代码的边界被进一步收窄
NativeAOT 是 .NET 这几年的主旋律之一。从 .NET 7 引入控制台和类库的 AOT 发布,到 .NET 8 支持 ASP.NET Core 的 AOT 模板,再到 .NET 9 的规模化改进,Preview 2 依然在路上。核心目标很简单:把中间语言直接编译成目标平台机器码,去掉运行时里的 JIT 和大部分元数据加载,换来的结果是启动时间大幅缩短、内存占用下降、容器镜像显著变小。
但代价也很明确:AOT 模式没办法在运行时随意生成代码,反射的能力被严格限制。在 Preview 2 中你可以看到大量工作集中在“如何在编译期静态分析出反射需求”,从而让裁剪器(Trimmer)和 AOT 编译器能保留必要的信息,而不是一刀切地禁止所有反射。
从实际应用来说,AOT 适合的函数包括:无状态的 API 网关、定时任务、函数计算、CLI 工具、侧车代理这类启动快、内存敏感、逻辑相对固定的服务。而像动态插件系统、重度依赖反射的 ORM、模板引擎这类场景,AOT 暂时还不是最优解。
2.3 运行时与类库的“减法”趋势:模块化、精简、不向后兼容的新思路
架构演进不光是加东西,更多时候是减东西。.NET 11 Preview 2 里就能看到一股清晰的“减法”趋势:把那些使用率低、维护成本高、和现代开发方式脱节的旧 API 逐步标记为废弃,同时推动运行时组件的模块化。
打个比方,以前的 .NET 像一个大工具箱,里面扳手、螺丝刀、电钻什么都有,不管用不用都给你背着。现在的方向是让你只带需要的工具:运行时在裁剪时能去掉未引用的代码,类库拆出更多独立的 NuGet 包,需要什么装什么。这直接体现在发布后的程序集数量、镜像层数、内存占用上。
当然这也意味着一些老代码在升级时会遇到 API 移除或行为变更。Preview 2 期最重要的不是马上改代码,而是把那些标记了 obsolete 的类型拉个清单,看看自己的代码仓库里踩到了哪些,提前做兼容性评估。
3. 核心细节逐项拆解:GC、异步、容器与安全
3.1 GC 与内存管理的改进方向,服务端场景收益最大
回到最实在的层面:GC 每次大版本都有小步快跑式的优化。Preview 2 里能看到的线索集中在几个方向:减少锁竞争、改进写屏障、优化大对象堆(LOH)的分配路径,以及在高并发场景下让 GC 暂停时间更可预测。
你在做压测时如果看到内存曲线呈锯齿状,不用担心,这是 GC 在工作。真正要关注的是“在一段时间内 GC 触发的频率”和“单次暂停的时长”。.NET 11 的改进方向就是让这两项在高负载下表现更稳定。对于内存分配极其频繁的服务,可以考虑在升级后多跑一轮 wrk 或者 k6 压测,对比 GC 指标和 P99 延迟,基本能看出收益。
另外,Server GC 的默认配置也在持续调优。多核机器上,GC 区域(Region)模式的覆盖范围扩大,能让内存回收更精准,减少 Full GC 带来的长暂停。这块没有银弹,最终效果和你服务的对象生命周期强相关。我的建议是:不要只看概念,拿自己的业务流量去测。
3.2 异步模型与线程池的变化,高并发下的 P99 更稳了
异步编程是 .NET 服务端应用的底子。Preview 2 中与异步相关的改进更多是内功型的:比如 Task 的快速路径优化、ValueTask 使用场景的扩展、AsyncLocal 访问成本的降低,以及线程池注入策略的持续调优。
很多团队遇到的高并发问题,表面看是“CPU 跑满”,实质却是线程池饥饿:线程被阻塞在 IO 上,新的请求排不上队。.NET 11 的线程池启发式算法会在这方面继续做文章,让线程注入和回收更加激进但又不至于频繁动荡。你可以在 Preview 2 里用 ThreadPool 指标配合 dotnet-counters 观察线程注入速率,看看在突发流量下是否比旧版本更快恢复稳定。
这里提醒一句:线程池再怎么优化,也救不了乱用同步阻塞的代码。在 ASP.NET Core 里,尽量避免在异步方法中调用 .Result 或 .Wait(),把 IO 密集型操作改成真正的 async/await,线程池才有机会发挥新版本的优势。
3.3 云原生场景的适配,镜像更薄、启动更快、非 root 运行更顺
云原生是 .NET 这些年最下功夫的地方之一。Preview 2 的容器相关改进,重点依然在镜像体积和启动速度上。结合 NativeAOT 发布,一个最简单的接口服务镜像可以控制在一二十兆级别,这比早期 .NET 镜像动辄几百兆已经有了天壤之别。
新版本还会继续推动容器内非 root 用户的运行配置。生产环境对安全要求高的团队,通常不愿意用 root 跑应用,但以前有些基础组件(比如某些诊断工具)在非 root 下会受限。Preview 2 中可以看到更多工具和运行时 API 开始适配非 root 场景,这对审计合规是一大利好。
另外,健康检查、优雅停机、配置绑定这些和编排系统打交道的部分,也在不断完善。如果你已经在用 Kubernetes,升级后要重点回归三个场景:滚动发布时的连接排空、启动探针的首次就绪时间、以及内存限制设置下的 OOM 行为。
3.4 安全、签名与依赖治理,供应链风险被更严肃地对待
现代软件开发离不开第三方依赖,依赖一多,供应链风险就上来了。Preview 2 在安全方向上的改进思路,是把依赖审计的能力继续往 CLI 和构建链路里埋:检查 NuGet 包签名、生成 SBOM(软件物料清单)、扫描已知漏洞等。
实操层面,你可以在构建脚本里提前接入这些检查。比如dotnet list package --vulnerable可以用来扫描已知漏洞;dotnet list package --deprecated可以找出已经过时的包。虽然这些命令在之前版本也能用,但新版本会把报告做得更细致,并能和 SBOM 生成打通,方便发版时同时输出一份依赖清单给安全团队。
安全这块常常被业务开发忽略,直到出了事才重视。但好在现在把安全检查嵌入 CI 并不难,成本很低,关键是团队愿不愿意坚持。
4. 动手实测:升级到 Preview 2 的完整流程与发布验证
4.1 安装与多版本共存,用 global.json 锁住项目版本
预览版的安装不建议直接用官网那个大而全的安装包,至少我更喜欢用 dotnet-install 脚本,方便指定版本和目录,也能随时卸载。
如果你已经装了 .NET 8/9/10 的 SDK,完全不用卸载旧的。SDK 支持多版本共存,你在项目根目录放一个 global.json 就能锁定新版本;不想要了,删掉 global.json 就回到原来版本。
# 安装 .NET 11 Preview 2 SDK(以 Linux/macOS 为例) curl -sSL https://dot.net/v1/dotnet-install.sh \ | bash -s -- --channel 11.0 --quality preview --runtime-dotnet # 查看当前机器上的 SDK dotnet --list-sdks装完之后记得把 dotnet 加到 PATH。Windows 上也可以用 PowerShell 版本的安装脚本,逻辑是一样的。预览版阶段不建议直接设成全局默认,除非你本来就只跑预览版环境。
4.2 更新 TargetFramework 与项目兼容性检查
升级现有项目到 .NET 11,最核心的动作是改目标框架。新建项目直接用net11.0,老项目需要手动把 TargetFramework 改掉。
<PropertyGroup> <TargetFramework>net11.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> </PropertyGroup>改完之后第一件事不是编译,而是跑一圈dotnet build看警告。预览版会对不兼容的 API 给出大量警告,有些是 obsolete 提示,有些是行为变更提醒。务必把警告当成错误来审,因为等 GA 之后这些警告会变成真正的 breaking change。
依赖包方面,很多库还没有 net11.0 的专门版本。此时不用慌,NuGet 的兼容规则允许 net11.0 项目引用 net10.0、net8.0 的包。但如果引用了更老框架的包,就要留意运行时行为差异。遇到按兵不动的包,可以先跑测试集,确认核心链路没问题后再继续。
4.3 用一个小例子实测 NativeAOT 发布,看看镜像和启动时间
我在本机用一个极简的 ASP.NET Core Web API 做了 NativeAOT 实测。项目文件里打开 AOT 发布开关:
<PropertyGroup> <PublishAot>true</PublishAot> <InvariantGlobalization>true</InvariantGlobalization> </PropertyGroup>然后执行发布命令:
dotnet publish -c Release -r linux-x64 -o ./publish/aot发布完成后观察输出目录:原生可执行文件加上几个必要文件,体积大概在一二十 MB 上下,具体取决于接口数量和依赖项。对比同代码的 JIT 发布(框架依赖模式)需要几百 MB 运行时环境,差异是很直观的。
启动测试我用的是/usr/bin/time配合脚本,连续启动 10 次取平均值。AOT 版本基本可以做到几十毫秒级启动,JIT 版本冷启动要慢不少。内存方面,AOT 版本因为不需要加载 JIT 和大量元数据,常驻内存也明显更低。
注意:NativeAOT 不是每个项目都能直接发布成功。如果你的代码里用了反射、动态程序集加载或者某些未标记裁剪友好的第三方库,发布时会收到警告甚至错误。遇到这种情况,先看警告信息中的类型名称,去 NuGet 上确认是否有新版或替代品。这是 Preview 版本最容易卡住的地方。
4.4 用 dotnet-counters 和 dotnet-trace 观察运行时指标
实测不能只看启动时间和体积,还得看运行指标。在目标机器上跑服务后,用诊断工具采集数据:
dotnet-counters monitor --process-id <pid> --counters System.Runtime这个命令能看到 CPU 使用率、GC 堆大小、线程池线程数、每秒 GC 次数、异常抛出次数等关键指标。升级到 Preview 2 后,我会重点关注两项:GC 暂停时间和线程池注入速率。如果 GC 暂停更短、线程注入更快恢复稳定,说明这版运行时在服务端高并发场景确实有正向改进。
更细的 CPU 热点分析可以用:
dotnet-trace collect --process-id <pid> --providers Microsoft-DotNETCore-SampleProfiler追踪文件可以用 speedscope 或 PerfView 打开。作为演示项目,跑一轮简单的压测就能看出 AOT 版本在 CPU 采样上的调用栈更短,很多运行时内部开销直接消失了。这也能帮团队判断,到底哪些服务适合上 AOT。
5. 预览版避坑实录:兼容性排查与快速回退清单
5.1 IDE 与 SDK 版本错位,本地开发环境最容易炸
预览版 SDK 装好后,不一定立刻能被你正在用的 IDE 完美识别。以常用开发工具为例,稳定版往往只支持正式版 SDK,预览版需要匹配预览体验通道。否则你打开项目时会发现无法加载或者提示 SDK 版本不被支持。
我的建议是:本地做 Preivew 2 测试时,要么单独准备好预览通道的环境,要么完全通过命令行加编辑器完成开发,不依赖 IDE 的图形化调试功能。调试器对预览版运行时的支持也存在滞后,遇到断点不命中不用意外,先把日志打出来做排查。
5.2 NuGet 包兼容性检查清单,老包不一定能跑
第三方包是升级路上最大变数。排名靠前的典型问题包括:
- 包只在 net8.0 下有构建产物,但依赖了 .NET 8 特有 API,在 net11.0 下虽然能编译,运行时会抛 MissingMethodException 或 TypeLoadException。
- 强依赖反射的库没有做裁剪配置,在 AOT 发布时频频警告,甚至生成出不可用的产物。
- 依赖底层版本有隐性要求(比如某版本开始才支持新线程池行为),升级后运行行为变化。
给一个简单实用的检查流程:
- 把项目里所有依赖用
dotnet list package --outdated拉一遍,看有没有预览或新版本。 - 逐个看包的目标框架,确认有 net10.0 或更高版本。
- 对核心链路涉及的包,用单元测试或集成测试覆盖关键路径。
- 发布 AOT 时,把裁剪警告全部记下来,逐个确认是否可接受。
5.3 NativeAOT 下的反射与代码生成问题,早发现早换方案
NativeAOT 最容易让人抓狂的就是运行时遇到“反射找不到类型”或者“无法创建代理”之类的报错。这通常是因为代码里某个框架或库在运行时动态生成了代码,而 AOT 发布时没有把相关元数据保存下来。
解决思路分三种层级:
- 第一层:升级依赖库到支持裁剪/AOT 的版本。
- 第二层:用源生成器替代运行时反射。
- 第三层:显式告诉裁剪器保留需要反射的类型,比如在项目里加入
DynamicDependency标注或 rd.xml 配置。
从效率角度,优先考虑替换方案而不是硬怼配置。反射在 AOT 模式下的限制是设计使然,硬保太多类型会让裁剪和 AOT 失去意义。
5.4 版本回退与反馈渠道,别把自己锁死在预览版
预览版期间最怕的就是“装上去回不来了”。其实回退很简单:只要 global.json 锁的版本改成你原来的 SDK 版本,再删掉 net11.0 的目标框架即可。不需要卸载 SDK,多版本共存就是为这种场景准备的。
万一 SDK 本身出了问题,卸载也很快。Linux 上删掉安装目录即可,Windows 走“添加或删除程序”。最稳妥的做法是让 CI 和生产环境的版本始终锁定在稳定版,只有预研环境放开到预览版。
另外,如果你在使用 Preview 2 时发现了问题,建议去官方 GitHub 仓库提 Issue。提 Issue 时尽量带上完整的运行时信息、SDK 版本号、可复现的最小项目和复现步骤。好的反馈不仅能帮到团队,也会让你在这个版本上少踩很多后续的坑。
我个人的体会是,.NET 11 Preview 2 最值得关注的东西,不是那几个新的 API,而是“性能回归检测成为默认机制”这件事本身。从这版开始,每个改动都带着基准数据进入主干,这种工程文化带来的积累效应,会在几年后体现成整个运行时质量的稳定提升。如果你正在评估要不要把生产服务迁到新版本,不用等 GA,现在就可以拿非生产环境跑一轮 AOT 发布和容器镜像压测,花一两周时间收集第一手兼容性数据。Preview 阶段踩坑的成本,永远比线上踩坑的成本低得多。