编程 .NET 11 Preview 4 深度解析:Runtime-Async 革命、进程 API 重写与 AI 原生 SDK 的全面进化

2026-05-17 06:13:43 +0800 CST views 4

.NET 11 Preview 4 深度解析:Runtime-Async 革命、进程 API 重写与 AI 原生 SDK 的全面进化

2026年5月12日,微软发布了 .NET 11 Preview 4。这不是一次修修补补的常规更新——Runtime-Async 全面铺开、Process API 从"八步走"到一行搞定、MCP Server 模板直接内置 SDK、Blazor 电路暂停、EF Core 向量搜索……每一个改动都在回答同一个问题:.NET 如何在 AI 时代保持企业级开发平台的生命力?

本文从底层运行时到上层应用框架,逐一拆解 Preview 4 的核心技术变更,配以完整代码示例和架构分析,帮你判断哪些特性值得现在就关注、哪些需要观望。


一、背景:.NET 11 的战略定位

在聊技术细节之前,先理解 .NET 11 在微软产品线中的位置。

.NET 每年一个正式版,11.0 计划在 2026 年 11 月 GA。从 Preview 1 到 Preview 4,我们能清晰看到三个战略方向:

  1. 运行时现代化:Runtime-Async 是 .NET 异步编程模型自 async/await 引入以来最大的底层重构
  2. AI 原生:MCP Server 模板内置、EF Core 向量搜索、ASP.NET Core AI 集成——AI 不再是"附加功能"
  3. 开发者体验:Process API 重写、dotnet watch 跨平台热重载、配置绑定增强——让日常开发更顺手

Preview 4 是一个"深度足够、覆盖面广"的版本,没有花哨的大噱头,但每个改动都指向实际痛点。下面按领域逐一深入。


二、Runtime-Async:.NET 异步编程的底层革命

2.1 什么是 Runtime-Async?

从 C# 5.0 引入 async/await 开始,编译器通过生成 异步状态机(Async State Machine) 来实现异步。每次你写一个 async 方法,编译器都会在背后生成一个 struct,包含状态字段、awaiter、moveNext 委托等一堆样板代码。这种方式工作得很好,但有代价:

  • 方法体积膨胀:每个 async 方法都会生成额外的 IL 代码
  • 调试复杂度:async 栈帧是合成的,调试器看到的调用栈和实际执行路径不一致
  • AOT 兼容性:状态机的复杂跳转模式让 NativeAOT 的优化空间受限

Runtime-Async 的核心思路是:把状态机的生成从编译期移到运行时

Preview 4 的标志性变更是:整个 .NET 运行时库和 ASP.NET Core 共享框架全部使用 runtime-async=on 编译。这意味着框架层所有的异步方法(FileStream.ReadAsync、HttpClient.GetAsync、DbContext.SaveChangesAsync 等)都不再生成传统的编译器状态机,而是依赖运行时原语直接调度。

2.2 对开发者意味着什么?

好消息是:你的代码不需要任何改动。async/await 语法不变,API 签名不变,行为语义不变。变化全部发生在底层。

但你能获得:

吞吐量提升:运行时调度比编译器生成的状态机更高效。框架方法(你每天都在调用但看不见实现的方法)的异步路径被优化了。

库体积缩减:不再为每个 async 方法生成状态机 IL,意味着框架 DLL 更小。对于 NativeAOT 场景(单文件发布、容器镜像),这直接转化为更小的二进制。

调试体验改善:运行时调度的栈帧更清晰,异步调试的"栈帧跳跃"问题有望缓解。

2.3 配套优化

Runtime-Async 不是孤立启用的,Preview 4 还带来了两个配套优化:

协变 Task → Task<T> 重写

// 基类
public abstract class BaseService
{
    public virtual Task ProcessAsync() { /* ... */ }
}

// 派生类 - 返回更具体的 Task<string>
public class MyService : BaseService
{
    public override Task<string> ProcessAsync() { /* ... */ }
}

以前这种协变返回需要编译器生成桥接代码。现在运行时自动处理,零开销。

Crossgen2 内联 Runtime-Async 方法

预编译(ReadyToRun/AOT)阶段现在可以对 runtime-async 方法执行内联优化。这减少了间接调用开销,对热路径性能至关重要。

2.4 需要注意的风险

如果你在 Preview 4 上遇到:

  • AsyncLocal 的行为与之前不一致
  • 异常栈帧的格式发生变化
  • 某些 async 方法在特定边界条件下表现异常

这很可能是 Runtime-Async 的边界问题。微软团队明确表示这是重点关注领域,遇到问题应该积极反馈。

2.5 实战:体验 Runtime-Async

目前 Runtime-Async 对开发者是透明的——你不需要修改任何代码。但你可以对比 Preview 3 和 Preview 4 的性能来验证收益:

using System;
using System.Diagnostics;
using System.Threading.Tasks;

BenchmarkRunner.Run<AsyncBenchmark>();

[MemoryDiagnoser]
public class AsyncBenchmark
{
    private readonly HttpClient _http = new();
    
    [Benchmark]
    public async Task<string> FetchDataAsync()
    {
        var response = await _http.GetAsync("https://httpbin.org/get");
        return await response.Content.ReadAsStringAsync();
    }
}

用 BenchmarkDotNet 分别在 .NET 10 和 .NET 11 Preview 4 上跑这个基准测试,关注 Allocated Memory 和 Throughput 的差异——框架层的异步优化会体现在这里。


三、Process API 重写:从"八步走"到一行搞定

3.1 旧 API 的痛点

任何一个写过 CLI 工具、构建脚本、DevOps 自动化的 .NET 开发者,都经历过启动子进程的痛苦:

// 旧写法 - "八步走"
var psi = new ProcessStartInfo("git", "status")
{
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    UseShellExecute = false,
    CreateNoWindow = true
};

using var process = Process.Start(psi)!;

var stdout = new StringBuilder();
var stderr = new StringBuilder();

process.OutputDataReceived += (s, e) =>
{
    if (e.Data != null) stdout.AppendLine(e.Data);
};

process.ErrorDataReceived += (s, e) =>
{
    if (e.Data != null) stderr.AppendLine(e.Data);
};

process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync();

if (process.ExitCode != 0)
    throw new Exception($"git failed: {stderr}");

八行配置 + 三行事件订阅 + 两行启动读取 + 一行等待 + 错误处理……就为了执行一个 git status。每次写都像在受刑。

3.2 新 API:一行搞定

Preview 4 彻底重写了进程管理 API:

// 新写法 - 一行搞定
var result = await Process.RunAndCaptureTextAsync("git", "status", "--porcelain");

Console.WriteLine($"Exit Code: {result.ExitStatus.ExitCode}");
Console.WriteLine($"Stdout:\n{result.StandardOutput}");
Console.WriteLine($"Stderr:\n{result.StandardError}");

从 15+ 行缩减到 3 行。这就是好的 API 设计——把常见的 80% 用法简化到极致,同时保留底层 API 给需要精细控制的 20% 场景。

3.3 完整 API 清单

API说明
Process.Run() / Process.RunAsync()启动进程并等待退出,返回 ProcessExitStatus
Process.RunAndCaptureText() / ...Async()启动 + 捕获 stdout/stderr 为文本
Process.ReadAllText() / Process.ReadAllBytes()启动 + 读取全部输出
Process.ReadAllLinesAsync()逐行流式读取,返回 ProcessOutputLine(区分 stdout/stderr)
Process.StartAndForget()启动后立即返回,自动 detach 句柄
ProcessStartInfo.KillOnParentExit (Windows)父进程退出时自动杀子进程
ProcessStartInfo.StartDetached子进程脱离父进程会话独立运行

3.4 实战:构建一个轻量级任务运行器

新 API 特别适合写任务运行器和 CLI 工具。下面是一个实用示例:

using System;
using System.Diagnostics;
using System.Threading.Tasks;

public class TaskRunner
{
    public record TaskResult(string Name, int ExitCode, string Output, double DurationMs);
    
    public async Task<TaskResult> RunAsync(string name, string command, params string[] args)
    {
        var sw = Stopwatch.StartNew();
        
        await foreach (var line in Process.ReadAllLinesAsync(command, args))
        {
            var prefix = line.IsError ? "[ERR] " : "[OUT] ";
            Console.WriteLine($"{prefix}[{name}] {line.Content}");
        }
        
        sw.Stop();
        
        // 重新获取退出码
        var result = await Process.RunAsync(command, args);
        return new TaskResult(name, result.ExitStatus.ExitCode, "", sw.Elapsed.TotalMilliseconds);
    }
    
    public async Task RunPipelineAsync(params (string name, string cmd, string[] args)[] tasks)
    {
        foreach (var (name, cmd, args) in tasks)
        {
            Console.WriteLine($"\n▶ Running: {name}");
            var result = await RunAsync(name, cmd, args);
            
            if (result.ExitCode != 0)
            {
                Console.WriteLine($"❌ {name} failed with exit code {result.ExitCode}");
                return;
            }
            
            Console.WriteLine($"✅ {name} completed in {result.DurationMs:F0}ms");
        }
        
        Console.WriteLine("\n🎉 All tasks completed successfully!");
    }
}

// 使用示例
var runner = new TaskRunner();
await runner.RunPipelineAsync(
    ("Build", "dotnet", new[] { "build", "--configuration", "Release" }),
    ("Test", "dotnet", new[] { "test", "--no-build" }),
    ("Publish", "dotnet", new[] { "publish", "-c", "Release", "-o", "./publish" })
);

这个模式在 CI/CD 脚本、本地开发工具链中非常常见。以前写这样的工具需要大量样板代码,现在核心逻辑清晰可见。

3.5 StartDetached:真正的后台进程

// 启动一个独立于当前终端的后台进程
var psi = new ProcessStartInfo("dotnet", "run")
{
    StartDetached = true
};

Process.Start(psi);
// 即使当前进程退出,dotnet run 也会继续运行

这在写 daemon、后台工作进程、自更新程序时非常有用。以前实现这个需要 P/Invoke 或复杂的会话管理。


四、MCP Server 模板内置 SDK:.NET 拥抱 AI Agent 标准

4.1 MCP 是什么?

MCP(Model Context Protocol)是 Anthropic 提出的开放协议,用于连接 AI 模型与外部工具和数据源。简单理解:

AI 客户端(Claude、GPT、Codex…)
    ↕ MCP 协议
MCP Server(你写的)
    ↕ HTTP/gRPC/本地调用
你的业务系统(数据库、API、文件系统…)

要让 AI 调用你的系统,不需要写 10 个不同 AI 平台的集成——写一个 MCP Server 就够了。OpenAI、Anthropic、Cursor、Windsurf 等主流 AI 客户端都已经或正在支持 MCP。

4.2 一行命令创建 MCP Server

Preview 4 把 MCP Server 模板直接内置到 .NET SDK:

dotnet new mcpserver -o MyAgentService
cd MyAgentService
dotnet run

生成的项目包含:

  • MCP 协议的完整实现(stdio 和 SSE transport)
  • 工具(Tool)注册的脚手架代码
  • 配置文件和调试支持

以前需要额外安装 Microsoft.McpServer.ProjectTemplates NuGet 包,现在是开箱即用——和 consolewebapiblazorserver 平起平坐。

4.3 实战:写一个数据库查询 MCP Server

// Tools/DatabaseTools.cs
using ModelContextProtocol.Server;

public class DatabaseTools
{
    private readonly IDbConnectionFactory _dbFactory;
    
    public DatabaseTools(IDbConnectionFactory dbFactory)
    {
        _dbFactory = dbFactory;
    }
    
    [McpServerToolType]
    public static class Queries
    {
        [McpServerTool, Description("查询用户订单列表")]
        public static async Task<string> GetOrders(
            [Description("用户ID")] string userId,
            [Description("最大返回数量")] int limit = 20)
        {
            // 实际项目中注入服务,这里简化
            await using var db = /* _dbFactory.CreateConnection() */;
            var orders = await db.QueryAsync<Order>(
                "SELECT * FROM orders WHERE user_id = @userId ORDER BY created_at DESC LIMIT @limit",
                new { userId, limit });
            
            return JsonSerializer.Serialize(orders, new JsonSerializerOptions 
            { 
                WriteIndented = true 
            });
        }
        
        [McpServerTool, Description("获取订单统计信息")]
        public static async Task<string> GetOrderStats(
            [Description("开始日期")] string startDate,
            [Description("结束日期")] string endDate)
        {
            // 统计逻辑...
            return """{"total": 1024, "completed": 980, "pending": 44}""";
        }
    }
}
// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMcpServer();

var app = builder.Build();
app.MapMcpServer();

app.Run();

AI 客户端连接后,就能直接调用 GetOrdersGetOrderStats 这两个工具——不需要写任何额外的 API 适配层。

4.4 这一步的战略意义

MCP Server 模板内置 SDK 看起来是个小改动,但信号意义很大:

  • 微软在选边站:没有搞封闭的 AI 集成方案,而是拥抱 MCP 这个开放协议
  • 从 .NET 8 的 Microsoft.Extensions.AI 到 Semantic Kernel 到现在的 MCP 模板,一条清晰的 AI 生态建设路径
  • 对于企业开发者:如果你有大量 .NET 后端系统想接入 AI Agent,现在门槛接近于零

五、Blazor:五个让生产环境体验飞跃的改进

5.1 Virtualize 终于不抖了

用过 <Virtualize> 做长列表的人都知道一个老毛病:当列表上方的某个元素高度变化(展开/折叠、异步加载完成),视口中的内容会突然跳动。这个 bug 困扰了 Blazor 开发者好几个版本。

Preview 4 用浏览器原生的 scroll anchoring 彻底解决了这个问题。不需要改任何代码,升级到 Preview 4 后自动修复。

5.2 AnchorMode:聊天界面的福音

新增的 AnchorMode 参数专门解决"列表前后插入数据时视口怎么处理":

@* 聊天界面:新消息追加时自动滚到底部 *@
<Virtualize Items="@messages" 
           AnchorMode="VirtualizeAnchorMode.End"
           ItemComparer="@_byId">
    <ItemContent Context="msg">
        <div class="message">@msg.Text</div>
    </ItemContent>
</Virtualize>

@* 新闻流:新内容插入顶部时保持当前视口 *@
<Virtualize Items="@articles"
           AnchorMode="VirtualizeAnchorMode.Beginning">
    <!-- ... -->
</Virtualize>

以前做聊天 UI 要写一堆 JS 手动控制滚动位置,现在一个属性搞定。这是一个"用过就回不去"的改进。

5.3 服务器端电路暂停

// 服务端主动暂停 Blazor 电路
await circuit.Features.Get<IHostCircuitFeature>()
    .RequestCircuitPauseAsync();

实际场景:服务器负载过高时,暂时断开非活跃用户的 Blazor 连接释放资源。用户切回页面时瞬间恢复,不会感知到中断。

对大规模 Blazor Server 部署(比如企业内部管理系统),这是运维层面的关键能力。

5.4 SupplyParameterFromTempData

@page "/order/confirm"

<StatusMessage />

@code {
    [SupplyParameterFromTempData]
    public string? StatusMessage { get; set; }
}

"提交后重定向再显示成功消息"这个极其常见的场景,终于不用再写 cookie hack 了。值在重定向上一次请求中自动保存和恢复。

5.5 Blazor Web Worker 改进

Web Worker 模板新增了两个关键能力:

// 前端 JS 调用 Worker(fire-and-forget)
await worker.invokeVoidAsync('processIn', data);

// 支持 CancellationToken 和超时
await worker.invokeAsync('longRunningOp', data, 
    cancellationToken, 
    TimeSpan.FromSeconds(30));

卡住的 Worker 可以干净地终止,不会再阻塞主线程。


六、Span-based 压缩 API:高性能场景的新选择

6.1 新 API 概览

Preview 4 新增了基于 Span<T> / ReadOnlySpan<T> 的压缩和解压 API:

using System.IO.Compression;

// 压缩 - 零中间分配
byte[] source = GetLargeData();
byte[] compressed = new byte[ZLibCompressor.GetMaxCompressedLength(source.Length)];
int compressedLength = ZLibCompressor.Compress(source, compressed);

// 解压
byte[] decompressed = new byte[originalLength];
int decompressedLength = ZLibDecompressor.Decompress(
    compressed.AsSpan(0, compressedLength), 
    decompressed);

6.2 支持的格式

格式编码器解码器
DeflateDeflateCompressorDeflateDecompressor
ZLibZLibCompressorZLibDecompressor
GZipGZipCompressorGZipDecompressor

6.3 与旧 API 的对比

// 旧方式 - 有中间分配
using var outputStream = new MemoryStream();
using (var gzipStream = new GZipStream(outputStream, CompressionLevel.Optimal))
{
    await gzipStream.WriteAsync(source);
}
byte[] compressed = outputStream.ToArray(); // 这里有一次 ToArray 分配

// 新方式 - 零分配(除了输入输出缓冲区)
int written = GZipCompressor.Compress(source, compressed);

对于网络中间件、消息队列消费者、日志处理管道等高吞吐场景,减少 GC 压力意味着更高的稳定吞吐量。

6.4 实战:高性能 HTTP 响应压缩中间件

public class SpanCompressionMiddleware
{
    private readonly RequestDelegate _next;
    
    public SpanCompressionMiddleware(RequestDelegate next) => _next = next;
    
    public async Task InvokeAsync(HttpContext context)
    {
        var originalBody = context.Response.Body;
        using var compressedStream = new MemoryStream();
        
        context.Response.Body = compressedStream;
        await _next(context);
        
        // 获取原始响应
        compressedStream.Position = 0;
        var originalBytes = compressedStream.ToArray();
        
        // 使用 Span API 压缩
        var maxLen = GZipCompressor.GetMaxCompressedLength(originalBytes.Length);
        var compressed = new byte[maxLen];
        int compressedLen = GZipCompressor.Compress(originalBytes, compressed);
        
        // 设置响应头并写入压缩后的内容
        context.Response.Headers.ContentEncoding = "gzip";
        context.Response.ContentLength = compressedLen;
        context.Response.Body = originalBody;
        
        await originalBody.WriteAsync(compressed.AsMemory(0, compressedLen));
    }
}

七、EF Core:SQL Server 向量搜索与 JSON 集成

7.1 SQL Server 2025 近似向量搜索

这是 EF Core 在 AI 时代的最重要的新能力——直接用 LINQ 查询向量数据库:

// 定义向量列
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public string Description { get; set; } = "";
    public Vector Embedding { get; set; } = null!;
}

// 语义搜索 - 找最相似的产品
float[] queryVector = await embeddingGenerator.GenerateAsync("高性能跑步鞋");

var similarProducts = await context.Products
    .OrderBy(p => p.Embedding.ApproximateDistance(queryVector))
    .Take(10)
    .ToListAsync();

// 带过滤条件的语义搜索
var results = await context.Products
    .Where(p => p.Category == "运动" && p.Price < 1000)
    .OrderBy(p => p.Embedding.ApproximateDistance(queryVector))
    .Take(5)
    .Select(p => new { p.Name, p.Price, Distance = p.Embedding.ApproximateDistance(queryVector) })
    .ToListAsync();

7.2 JSON 映射集成到关系模型

// 实体定义 - JSON 列完全融入关系模型
public class Order
{
    public int Id { get; set; }
    public List<OrderItem> Items { get; set; } = new(); // JSON 列
    public Dictionary<string, string> Metadata { get; set; } = new(); // JSON 列
    public DateTime CreatedAt { get; set; }
}

// LINQ 查询 - JSON 字段可以像普通属性一样查询
var recentOrders = await context.Orders
    .Where(o => o.Items.Count > 3)  // JSON 数组长度过滤
    .Where(o => o.Metadata["source"] == "mobile")  // JSON 字段过滤
    .OrderByDescending(o => o.CreatedAt)
    .ToListAsync();

JSON 列不再是"二等公民"——它们完全融入查询、跟踪和迁移管道。这意味着你可以在关系模型中灵活使用 JSON 存储半结构化数据,同时享受强类型的 LINQ 查询体验。

7.3 时态表支持

public class AuditRecord
{
    public int Id { get; set; }
    public string Content { get; set; } = "";
    public DateTime PeriodStart { get; set; }  // 自动映射
    public DateTime PeriodEnd { get; set; }    // 自动映射
}

// 查询某个时间点的数据
var historicalData = await context.AuditRecords
    .TemporalAsOf(specificDateTime)
    .Where(r => r.Content.Contains("error"))
    .ToListAsync();

SQL Server 时态表的 PeriodStart/PeriodEnd 列现在可以显式映射到 CLR 属性,配合 TemporalAsOf 查询,实现完整的数据历史追溯。


八、JIT 编译器与运行时优化

8.1 常量折叠 SequenceEqual

// JIT 在编译期就能确定结果
if ("hello".SequenceEqual("hello"))  // 直接优化为 true
{
    // 这个分支在 JIT 后是确定会执行的
}

// 运行时比较也能受益
const string Version = "2.0.0";
if (header.SequenceEqual(Version))  // 编译期折叠

这不是什么惊天动地的优化,但在热路径上频繁调用的字符串比较场景(比如协议解析、路由匹配),累积效果可观。

8.2 SIMD 代码生成改进

JIT 编译器对 SIMD(Single Instruction Multiple Data)的代码生成持续改进,这意味着:

  • Vector256<T>Vector512<T> 的操作生成更高效的 SIMD 指令
  • 内存对齐处理更智能
  • 跨平台 SIMD 行为一致性更好

对于图像处理、加密、数值计算等场景,这可能带来 2-5x 的性能提升。

8.3 支持 1024+ CPU

.NET 之前有 64 个 CPU 组的限制,在超过 1024 核的机器上会出问题。Preview 4 打破了限制,面向云原生的超大实例(AWS u-* 系列实例、Azure HBv4 系列等)。

绝大多数人用不上,但这说明 .NET 在认真对待高性能计算场景。


九、SDK 和工具链改进

9.1 dotnet watch 支持移动开发热重载

# 交互式选择目标设备
dotnet watch --device

# 输出类似:
# ? Select a device to run on:
# > 📱 Pixel 8 (Android 14) - emulator-5554
#   📱 iPhone 15 Pro (iOS 17.5) - simulator-xxxx
#   🖥️  macOS

修改 XAML 或 C# 代码后,变更自动推送到运行中的模拟器或物理设备。这让 MAUI 开发的迭代速度大幅提升——终于不再需要每次修改都重新编译部署了。

9.2 Fish Shell 补全

# Fish 用户现在也有命令补全了
dotnet <TAB>  # 列出所有子命令
dotnet build <TAB>  # 列出 build 选项

之前只有 Bash、Zsh、PowerShell 有补全支持。Fish 用户终于不用羡慕了。

9.3 FORCE_COLOR 支持

# 管道操作中保留彩色输出
FORCE_COLOR=1 dotnet run | tee build.log

以前 dotnet run | tee 这种管道操作会让颜色消失。现在遵循业界标准的 FORCE_COLOR 环境变量。

9.4 OpenTelemetry 遥测迁移

.NET CLI 内部遥测从 Application Insights 迁移到 OpenTelemetry。这不是用户直接能感知的改动,但说明微软在内部也在拥抱可观测性标准。


十、HTTP QUERY 方法:复杂搜索的优雅解决方案

10.1 问题背景

REST API 设计中有一个经典的两难:

  • 用 GET:搜索条件太多时 URL 会很长,可能超过浏览器和服务器的 URL 长度限制
  • 用 POST:语义不对,POST 应该用于创建资源,不是查询

HTTP QUERY 方法就是为此而生的——它是一个安全的幂等方法,但允许在请求体中发送查询条件:

// 注册 HTTP QUERY 端点
app.MapMethods("/api/products/search", ["QUERY"], async (
    [FromBody] ProductSearchRequest request,
    ProductDbContext db) =>
{
    var query = db.Products.AsQueryable();
    
    if (!string.IsNullOrEmpty(request.Keyword))
        query = query.Where(p => p.Name.Contains(request.Keyword));
    
    if (request.MinPrice.HasValue)
        query = query.Where(p => p.Price >= request.MinPrice.Value);
    
    if (request.MaxPrice.HasValue)
        query = query.Where(p => p.Price <= request.MaxPrice.Value);
    
    if (request.CategoryIds?.Length > 0)
        query = query.Where(p => request.CategoryIds.Contains(p.CategoryId));
    
    // ... 更多过滤条件
    
    return Results.Ok(await query
        .OrderBy(p => p.RelevanceScore)
        .Skip((request.Page - 1) * request.PageSize)
        .Take(request.PageSize)
        .ToListAsync());
});

public record ProductSearchRequest(
    string? Keyword,
    decimal? MinPrice,
    decimal? MaxPrice,
    int[]? CategoryIds,
    string? SortBy,
    SortDirection SortDirection,
    int Page = 1,
    int PageSize = 20
);

10.2 OpenAPI 支持

ASP.NET Core 的 OpenAPI 文档生成器现在能正确识别 HTTP QUERY 方法,Swagger UI 和其他 API 文档工具可以正常展示这类端点。

10.3 客户端调用

// fetch
fetch('/api/products/search', {
    method: 'QUERY',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ keyword: '蓝牙耳机', minPrice: 100, maxPrice: 500 })
});

// HttpClient (C#)
var response = await client.SendAsync(new HttpRequestMessage(
    new HttpMethod("QUERY"), 
    "/api/products/search")
{
    Content = JsonContent.Create(new { Keyword = "蓝牙耳机", MinPrice = 100 })
});

十一、其他值得关注的改进

11.1 浮点数十六进制格式化

// IEEE 754 十六进制表示
double pi = Math.PI;
Console.WriteLine(pi.ToString("X16")); // 0x1.921fb54442d18p+1

// 解析回来
double parsed = double.Parse("0x1.921fb54442d18p+1", 
    NumberStyles.HexNumber, CultureInfo.InvariantCulture);

这在科学计算、二进制数据交换协议中很实用——精确表示浮点数的二进制形式,没有十进制转换的精度损失。

11.2 F# 联合类型 JSON 序列化

// F# 定义
type Shape = 
    | Circle of radius: float
    | Rectangle of width: float * height: float

// C# 端直接反序列化,不需要自定义 converter
var shape = JsonSerializer.Deserialize<Shape>(json);

F# 和 C# 混合开发的项目直接受益。

11.3 MemoryCache 内置 OpenTelemetry 指标

services.AddMemoryCache()
    .AddOpenTelemetry();  // 自动暴露缓存指标

暴露的指标包括:

  • dotnet.cache.requests (带 hit/miss 标签)
  • dotnet.cache.evictions
  • dotnet.cache.entries
  • dotnet.cache.estimated_size

Grafana 仪表盘直接接,不用再自己包一层指标收集。

11.4 Kestrel TLS 握手故障诊断

// 以前 TLS 握手失败就是个 IOException
// 现在能拿到具体原因
app.Run(async (context) =>
{
    var tlsFeature = context.Features.Get<ITlsHandshakeFeature>();
    if (tlsFeature != null && tlsFeature.Exception != null)
    {
        logger.LogWarning(tlsFeature.Exception, 
            "TLS handshake failed: {Reason}", tlsFeature.Exception.Message);
    }
});

线上排查 SSL/TLS 问题终于不用瞎猜了。

11.5 ConfigurationIgnore 属性

public class AppOptions
{
    public string Endpoint { get; set; } = "";
    
    [ConfigurationIgnore]  // 这个属性不参与配置绑定
    public string ComputedKey => Endpoint + ":default";
}

比以前用各种 hack 排除计算属性干净多了。

11.6 HttpClient HTTP/2 自动降级

在 Windows 认证(NTLM/Negotiate)场景下,HttpClient 自动从 HTTP/2 降级到 HTTP/1.1。因为 Windows 认证机制不支持 HTTP/2 多路复用,以前会直接报错。企业内网应用的福音。


十二、性能优化建议

基于 Preview 4 的变化,以下是一些实际可用的性能优化建议:

12.1 迁移到 Span 压缩 API

如果你有高频的压缩/解压操作(日志管道、消息队列、HTTP 中间件),从 GZipStream 迁移到 GZipCompressor.Compress()

// Before: 每次 ~200ns + 分配
using var ms = new MemoryStream();
using (var gz = new GZipStream(ms, CompressionLevel.Fastest))
    gz.Write(data);
var result = ms.ToArray();

// After: 每次 ~80ns + 零中间分配
var buffer = new byte[GZipCompressor.GetMaxCompressedLength(data.Length)];
int len = GZipCompressor.Compress(data, buffer);

12.2 利用 MemoryCache 内置指标

不再需要自己写缓存监控:

// 只需配置 OpenTelemetry 导出
builder.Services.AddOpenTelemetry()
    .WithMetrics(metrics => metrics
        .AddMeter("Microsoft.Extensions.Caching.Memory"));

// Prometheus/Grafana 自动拿到缓存命中率等指标

12.3 审查 async 方法的性能

升级到 Preview 4 后,用 BenchmarkDotNet 重新测量你的热路径。Runtime-Async 的优化可能让你的高吞吐场景(比如消息消费者、API 网关)自动获得 5-15% 的吞吐量提升。

12.4 1024+ CPU 场景

如果你在 AWS/Azure 的大实例上运行 .NET,升级到 Preview 4 可以解除 CPU 核数限制。


十三、迁移指南

13.1 升级步骤

# 安装 .NET 11 Preview 4 SDK
# macOS
brew install dotnet@11-preview
# 或直接下载
# https://dotnet.microsoft.com/download/dotnet/11.0

# 更新 global.json
dotnet new globaljson --sdk-version 11.0.100-preview.4.xxxxx

# 更新 TargetFramework
# <TargetFramework>net11.0</TargetFramework>

# 升级 NuGet 包
dotnet list package --outdated
dotnet add package Microsoft.AspNetCore.App --version 11.0.0-preview.4.*
dotnet add package Microsoft.EntityFrameworkCore --version 11.0.0-preview.4.*

13.2 可能的破坏性变更

  • Runtime-Async 行为差异:极少数情况下 async 方法的行为可能与之前版本不同,特别是在复杂的异常处理和 AsyncLocal 场景
  • Process API 变更:新增的 API 是增量添加的,旧 API 仍然可用,不存在破坏性变更
  • Blazor 虚拟化:scroll anchoring 行为变化可能在极少数自定义滚动方案中需要调整

13.3 不建议立即升级的场景

  • 生产环境的稳定运行系统——等 GA
  • 对异步行为有精细依赖的系统——等 Runtime-Async 更成熟
  • MAUI 生产应用——iOS 热重载还有已知限制

十四、总结与展望

14.1 Preview 4 的核心信号

回顾整个 Preview 4,三个趋势非常清晰:

第一,运行时现代化是大方向。 Runtime-Async 的全面铺开不是一个小优化——它代表了 .NET 团队对异步编程模型的一次深层次反思和重构。从编译器状态机到运行时调度,这个转变的完成度将在后续预览版中逐步显现。

第二,AI 是一等公民。 MCP Server 模板内置 SDK、EF Core 向量搜索、ASP.NET Core AI 集成——这些不是零散的实验性功能,而是一条完整的 AI 开发者体验路线图。.NET 在 AI 时代的定位很明确:企业级 AI 应用的最佳运行平台。

第三,开发者体验是持续的优先级。 Process API 从"八步走"到一行搞定、Virtualize 不抖了、dotnet watch 支持移动热重载——这些"小"改进直接影响每天写代码的体验。积少成多,这就是让人愿意继续用 .NET 的理由。

14.2 对不同角色的建议

角色应该关注什么
后端开发者Runtime-Async 性能、Process API、Span 压缩
AI 工程师MCP Server 模板、EF Core 向量搜索
前端/全栈Blazor Virtualize 修复、AnchorMode
移动开发者MAUI dotnet watch 热重载
DevOps1024+ CPU 支持、OpenTelemetry 指标
架构师.NET AI 战略布局、HTTP QUERY 标准

14.3 展望 GA

.NET 11 预计在 2026 年 11 月正式发布。从 Preview 4 的成熟度来看,核心特性基本定型。建议在 Preview 5/6 阶段重点关注:

  • Runtime-Async 的性能数据和稳定性
  • NativeAOT 与 Runtime-Async 的配合效果
  • EF Core 向量搜索的 LINQ 表达式完善度
  • MAUI 热重载的 iOS 限制是否解除

Preview 4 已经展现出相当成熟的技术方向,值得开始评估和试用。尤其是 Process API 和 MCP Server 模板,这两个特性现在就能在生产项目中使用——前者解决的是每天都会遇到的痛点,后者为即将到来的 AI Agent 爆发做好准备。


参考资源

复制全文 生成海报 .NET C# Runtime-Async MCP Server Blazor EF Core

推荐文章

Golang中国地址生成扩展包
2024-11-19 06:01:16 +0800 CST
Vue 中如何处理跨组件通信?
2024-11-17 15:59:54 +0800 CST
pin.gl是基于WebRTC的屏幕共享工具
2024-11-19 06:38:05 +0800 CST
php客服服务管理系统
2024-11-19 06:48:35 +0800 CST
黑客帝国代码雨效果
2024-11-19 01:49:31 +0800 CST
Vue3中的Slots有哪些变化?
2024-11-18 16:34:49 +0800 CST
Nginx 实操指南:从入门到精通
2024-11-19 04:16:19 +0800 CST
程序员茄子在线接单