.NET 11 Preview 4 深度实战:Runtime-Async 革命、Process API 重生、MCP Server 模板——微软如何用一次预览版重新定义后端开发范式
2026 年 5 月 12 日,微软发布 .NET 11 Preview 4。这不是一次常规的版本迭代——Runtime-Async 全面启用意味着 .NET 的异步编程模型从编译器时代迈入运行时时代,Process API 的大规模扩展让系统级编程不再痛苦,MCP Server 模板让 .NET 开发者一键拥抱 AI Agent 生态。本文将从架构原理到代码实战,逐层拆解这次更新的每一个技术细节。
一、为什么 Preview 4 值得你放下手上的活
每次 .NET 发预览版,大部分开发者扫一眼 release notes 就关了。但 Preview 4 不一样——它包含了三个足以改变你写代码方式的变更:
- Runtime-Async 全面启用:异步方法不再生成状态机,运行时直接调度。这不是语法糖,是执行模型的根本转变。
- Process API 大规模扩展:一行代码替代过去几十行的进程调用样板代码。
- MCP Server 模板:
dotnet new mcp-server,你的 .NET 服务立刻成为 AI Agent 的工具提供者。
加上 Blazor 电路暂停/恢复、EF Core 向量搜索、1024+ CPU 支持……这不是小修小补,这是微软在给 .NET 11 正式版铺路,告诉全世界:.NET 不只是"企业级"的代名词,它要成为 AI 时代的首选后端。
二、Runtime-Async:异步编程的第二次革命
2.1 异步编程的前世今生
.NET 的异步编程经历了三个阶段:
第一阶段:APM(Asynchronous Programming Model)
// .NET 1.0 时代,痛苦得令人发指
IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state);
int EndRead(IAsyncResult asyncResult);
每个操作要写 Begin/End 两个方法,回调地狱是家常便饭。
第二阶段:async/await + 状态机(.NET 4.5 起)
// 编译器帮你生成状态机,代码好看多了
public async Task<string> GetDataAsync(string url)
{
var response = await httpClient.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
编译器把 async 方法转成一个 IAsyncStateMachine 实现,每次 await 都是一次状态转换。好用了,但有代价:
- 每个异步方法生成一个状态机类,增加程序集体积
- 状态机中的字段(局部变量、awaiter 等)都在堆上分配
- 状态转换本身有开销(虽然 .NET 团队一直在优化)
第三阶段:Runtime-Async(.NET 11 Preview 4)
这就是现在发生的事。核心变化:编译器不再为异步方法生成状态机,异步调用链由运行时直接调度。
2.2 Runtime-Async 的工作原理
传统 async/await 的编译过程:
源代码 async Task Foo() { await Bar(); }
↓ C# 编译器
生成一个 IAsyncStateMachine 实现(MoveNext、SetStateMachine 等)
↓ CLR
通过 AsyncTaskMethodBuilder 驱动状态机执行
Runtime-Async 的编译过程:
源代码 async Task Foo() { await Bar(); }
↓ C# 编译器(runtime-async=on)
不生成状态机,生成轻量级异步帧(async frame)
↓ CoreCLR Runtime
运行时异步调度器直接管理异步帧的生命周期和调度
关键区别在于:状态机是编译时的产物,异步帧是运行时的原语。 运行时可以根据实际执行情况做动态优化(比如内联、复用帧等),而编译时生成的状态机是静态的,无法根据运行时信息调整。
2.3 实际影响有多大
微软在 release notes 中提到了两个核心收益:
吞吐量提升:异步调用链越深,收益越大。在微服务场景中,一个请求可能经历 10+ 层异步调用,每层省一点就是显著的吞吐提升。
库体积缩减:不再生成状态机类,意味着每个异步方法少了一个嵌套类。在大项目中,这可能减少数 MB 的程序集体积。
让我们写个基准测试来验证:
// BenchmarkDotNet 测试
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
[MemoryDiagnoser]
public class AsyncBenchmark
{
[Benchmark(Baseline = true)]
public async Task<int> TraditionalAsync()
{
var sum = 0;
for (int i = 0; i < 100; i++)
{
sum += await ComputeAsync(i);
}
return sum;
}
[Benchmark]
public async Task<int> RuntimeAsync()
{
// 同样的代码,但使用 runtime-async=on 编译
var sum = 0;
for (int i = 0; i < 100; i++)
{
sum += await ComputeAsync(i);
}
return sum;
}
private async Task<int> ComputeAsync(int n)
{
await Task.Yield();
return n * 2;
}
}
// 运行方式:
// 1. 用传统方式编译:dotnet run -c Release
// 2. 用 runtime-async 编译:在 .csproj 中添加 <RuntimeAsync>on</RuntimeAsync>,然后 dotnet run -c Release
2.4 协变 Task 重写:解决一个十年痛点
.NET 一直有个尴尬的限制:如果基类方法返回 Task<object>,派生类方法不能返回 Task<string>(虽然 string 是 object 的子类)。这是因为 Task<T> 不是协变的。
Preview 4 的解决方案:运行时自动生成桥接 thunk。
// 基类
public abstract class Repository
{
public abstract Task<object> GetByIdAsync(int id);
}
// 派生类 — 以前不行,现在可以了
public class UserRepository : Repository
{
// 返回 Task<User>,运行时自动生成 Task<User> → Task<object> 的桥接
public override async Task<User> GetByIdAsync(int id)
{
return await _db.Users.FindAsync(id);
}
}
运行时在方法入口处插入一个轻量级的 thunk,把 Task<User> 转成 Task<object>,不需要开发者手动 .ContinueWith(t => (object)t.Result) 这种丑陋的写法。
2.5 Crossgen2 内联 Runtime-Async 方法
Crossgen2 是 .NET 的预编译工具(ReadyToRun / AOT 编译器)。Preview 4 让 Crossgen2 能够内联 runtime-async 方法,这意味着:
- 预编译阶段就能确定异步调用链的优化路径
- 减少方法调用开销(内联后直接执行,没有 call/ret)
- 对 AOT 场景(Native AOT)尤为重要——启动时间更短
2.6 迁移注意事项
Runtime-Async 默认在 .NET 11 Preview 4 中对所有运行时库启用,但你自己代码中的异步方法默认仍然使用传统状态机。要启用,需要在 .csproj 中添加:
<PropertyGroup>
<RuntimeAsync>on</RuntimeAsync>
</PropertyGroup>
迁移风险:
- 如果你的代码依赖异步状态机的具体实现细节(比如反射访问
<>1__state字段),会出问题 - 如果你有自定义的
IAsyncStateMachine实现,需要测试兼容性 - 调试体验可能暂时不如传统方式(断点、变量查看等)
建议:新项目直接开启,老项目先在测试环境验证。
三、Process API:系统级编程的重生
3.1 旧 API 有多痛
在 Preview 4 之前,用 System.Diagnostics.Process 执行一个命令并获取输出,你需要写这样的代码:
// 旧方式 — 这还只是最简版本
public static async Task<string> RunCommandAsync(string fileName, string arguments)
{
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = fileName,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
var outputBuilder = new StringBuilder();
var errorBuilder = new StringBuilder();
process.OutputDataReceived += (_, e) =>
{
if (e.Data != null) outputBuilder.AppendLine(e.Data);
};
process.ErrorDataReceived += (_, e) =>
{
if (e.Data != null) errorBuilder.AppendLine(e.Data);
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
await process.WaitForExitAsync();
if (process.ExitCode != 0)
{
throw new InvalidOperationException(
$"Process failed with exit code {process.ExitCode}: {errorBuilder}");
}
return outputBuilder.ToString();
}
30 行代码,只是执行一个命令然后拿输出。这在 Python 里是 subprocess.run() 一行的事。
3.2 新 API:终于像现代语言了
Preview 4 新增的 Process.Run 系列方法:
// 一行执行,一行拿结果
var result = await Process.RunAndCaptureTextAsync("git", "log --oneline -5");
Console.WriteLine(result.StandardOutput);
Console.WriteLine($"Exit code: {result.ExitCode}");
// 读取全部输出为文本
var output = await Process.ReadAllText("kubectl", "get pods -n default");
// 读取全部输出为字节数组
var bytes = await Process.ReadAllBytes("openssl", "dgst -sha256 myfile.tar.gz");
// 逐行读取,自动区分 stdout 和 stderr
await foreach (var line in Process.ReadAllLinesAsync("dotnet", "build"))
{
var prefix = line.IsError ? "[ERR]" : "[OUT]";
Console.WriteLine($"{prefix} {line.Content}");
}
3.3 ProcessOutputLine 结构体详解
public readonly struct ProcessOutputLine
{
public string Content { get; init } // 行内容
public bool IsError { get; init } // 是否来自 stderr
public int LineNumber { get; init } // 行号(从 1 开始)
}
这个设计很精妙——用 IAsyncEnumerable<ProcessOutputLine> 返回,你可以在流式场景中实时处理输出,不需要等进程结束。对于长时间运行的进程(如 build、test),这意味着你可以实时展示进度。
3.4 实战:构建一个实时构建日志处理器
public class BuildLogProcessor
{
public async Task<BuildResult> BuildAndAnalyzeAsync(string projectPath)
{
var errors = new List<ProcessOutputLine>();
var warnings = new List<ProcessOutputLine>();
var stopwatch = Stopwatch.StartNew();
await foreach (var line in Process.ReadAllLinesAsync(
"dotnet", $"build {projectPath} --no-incremental"))
{
if (line.IsError)
{
// 区分错误和警告
if (line.Content.Contains("warning CS"))
warnings.Add(line);
else
errors.Add(line);
}
// 实时输出到控制台(带颜色)
var color = line.IsError ? ConsoleColor.Red : ConsoleColor.Gray;
Console.ForegroundColor = color;
Console.WriteLine(line.Content);
Console.ResetColor();
}
stopwatch.Stop();
return new BuildResult
{
ErrorCount = errors.Count,
WarningCount = warnings.Count,
Duration = stopwatch.Elapsed,
Errors = errors,
Warnings = warnings
};
}
}
public record BuildResult
{
public int ErrorCount { get; init; }
public int WarningCount { get; init; }
public TimeSpan Duration { get; init; }
public IReadOnlyList<ProcessOutputLine> Errors { get; init; }
public IReadOnlyList<ProcessOutputLine> Warnings { get; init; }
}
3.5 Process.Run 同步版本的使用场景
// 同步版本适用于脚本、CLI 工具等场景
var result = Process.RunAndCaptureText("docker", "ps --format '{{.Names}}'");
var containers = result.StandardOutput.Split('\n', StringSplitOptions.RemoveEmptyEntries);
foreach (var container in containers)
{
Console.WriteLine($"Stopping {container}...");
Process.Run("docker", $"stop {container}");
}
3.6 与其他语言的对比
| 语言 | 执行命令并获取输出 | 代码行数 |
|---|---|---|
| Python | subprocess.run(["git", "log"], capture_output=True, text=True) | 1 |
| Go | exec.Command("git", "log").Output() | 1 |
| Rust | Command::new("git").arg("log").output() | 1 |
| .NET (旧) | 30+ 行样板代码 | 30+ |
| .NET (Preview 4) | await Process.RunAndCaptureTextAsync("git", "log") | 1 |
从"最啰嗦"到"与其他语言平齐",.NET 补上了这个缺失了 20 年的 API。这不是小改进,这是对开发体验的根本性修复。
四、ASP.NET Core:三线并进
4.1 OpenAPI 支持 HTTP QUERY 方法
HTTP QUERY 是一个即将标准化的新 HTTP 方法(RFC 9110 之后的扩展草案)。与 GET 带查询参数不同,QUERY 方法允许在请求体中发送查询条件,同时保持 GET 的安全性和幂等性。
// 定义 QUERY 端点
app.MapQuery("/api/products/search", (SearchRequest request) =>
{
return dbContext.Products
.Where(p => p.Category == request.Category)
.Where(p => p.Price >= request.MinPrice)
.Take(request.PageSize)
.ToList();
});
public record SearchRequest
{
public string Category { get; init; } = "";
public decimal MinPrice { get; init; }
public int PageSize { get; init; } = 20;
}
客户端调用:
QUERY /api/products/search HTTP/1.1
Content-Type: application/json
{
"category": "electronics",
"minPrice": 100,
"pageSize": 20
}
与 POST 的区别:QUERY 是安全方法和幂等方法,可以被缓存,可以被预取。这对搜索引擎、数据查询类 API 来说是更好的语义选择。
4.2 Blazor 电路暂停/恢复——大规模部署的关键能力
这是我最看重的 Blazor 改进。在之前的版本中,Blazor Server 的电路(circuit)一旦断开(比如用户切到其他标签页太久),状态就丢失了。
Preview 4 引入了电路暂停/恢复机制:
// 在 Program.cs 中配置电路暂停策略
builder.Services.AddServerSideBlazor(options =>
{
options.CircuitOptions.MaxRetriedDisconnections = 3;
options.DisconnectTimeout = TimeSpan.FromMinutes(5);
// 新增:允许服务器主动暂停电路
options.AllowCircuitSuspension = true;
});
// 服务器端:在负载过高时暂停非活跃电路
public class CircuitManagerService
{
private readonly ConcurrentDictionary<string, CircuitState> _circuits = new();
public void SuspendIdleCircuits(TimeSpan idleThreshold)
{
foreach (var (circuitId, state) in _circuits)
{
if (DateTime.UtcNow - state.LastActivity > idleThreshold)
{
state.Suspend();
// 释放服务器资源(内存、SignalR 连接等)
// 客户端保持状态,用户回来时自动恢复
}
}
}
}
实际场景:一个在线教育平台,1 万名学生同时在线看直播,但只有 1000 人在互动。服务器可以暂停 9000 个非活跃电路,将内存占用从 50GB 降到 5GB。
4.3 MCP Server 模板:.NET 拥抱 AI Agent 生态
MCP(Model Context Protocol)是 Anthropic 提出的标准协议,用于 AI 模型与外部工具/数据源的交互。类似于 USB-C 统一了充电接口,MCP 试图统一 AI Agent 与外部世界的连接方式。
# 一行命令创建 MCP Server 项目
dotnet new mcp-server -o MyAgentService
cd MyAgentService
dotnet run
生成的项目模板包含:
// 自动生成的 MCP Server 骨架
using ModelContextProtocol.Server;
var builder = WebApplication.CreateBuilder(args);
// 注册 MCP 服务
builder.Services.AddMcpServer(options =>
{
options.ServerName = "MyAgentService";
options.ServerVersion = "1.0.0";
});
// 注册工具
builder.Services.AddMcpTool<ProductSearchTool>();
builder.Services.AddMcpTool<OrderQueryTool>();
var app = builder.Build();
// 映射 MCP 端点
app.MapMcp();
app.Run();
// 定义工具
[McpTool("product_search", "搜索产品信息")]
public class ProductSearchTool
{
[McpToolMethod]
public async Task<string> SearchAsync(
[McpParam("query", "搜索关键词")] string query,
[McpParam("limit", "返回数量", DefaultValue = 10)] int limit)
{
// 实现搜索逻辑
var results = await searchService.SearchAsync(query, limit);
return JsonSerializer.Serialize(results);
}
}
为什么这对 .NET 开发者重要?
- 企业内部有大量 .NET 服务,这些服务现在可以零成本地成为 AI Agent 的工具提供者
- MCP 协议正在被 Claude、GPT 等主流 AI 采纳,先接入先受益
- 相比从零实现 MCP 协议,模板把启动时间从"几天"压缩到"几分钟"
4.4 Blazor 其他改进
[SupplyParameterFromTempData]:
@page "/order/{id:int}"
@attribute [SupplyParameterFromTempData]
public string? SuccessMessage { get; set; }
<div class="alert alert-success" style="display:@(SuccessMessage != null ? "block" : "none")">
@SuccessMessage
</div>
@code {
[Parameter]
public int Id { get; set; }
}
// 在另一个页面设置 TempData
TempData["SuccessMessage"] = "订单创建成功!";
return RedirectToPage("/order/123");
Virtualize 组件改进:
<Virtualize Items="@allProducts" Context="product" AnchorMode="AnchorMode.Start">
<ItemContent>
<ProductCard Product="@product" />
</ItemContent>
<Placeholder>
<LoadingSkeleton />
</Placeholder>
</Virtualize>
@code {
// 当上方内容变化时(如筛选),保持视口稳定
// AnchorMode.Start: 以列表顶部为锚点
// AnchorMode.End: 以列表底部为锚点
// 这解决了虚拟化列表在数据更新时"跳动"的问题
}
五、EF Core:向量搜索来了
5.1 SQL Server 2025 近似向量搜索
// 定义实体
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Description { get; set; } = "";
// 向量嵌入列 — SQL Server 2025 的 vector 类型
public Embedding? Embedding { get; set; }
}
// 配置
modelBuilder.Entity<Product>(entity =>
{
entity.Property(p => p.Embedding)
.HasColumnType("vector(1536)"); // 1536 维,对应 OpenAI text-embedding-ada-002
});
// 执行近似向量搜索
public class ProductSearchService
{
private readonly AppDbContext _context;
private readonly IEmbeddingService _embeddingService;
public async Task<List<Product>> SearchSimilarAsync(string query, int topK = 10)
{
// 1. 将查询文本转换为向量
var queryVector = await _embeddingService.GetEmbeddingAsync(query);
// 2. 使用 EF Core 的向量搜索 API
var results = await _context.Products
.OrderBy(p => p.Embedding!.ApproximateDistance(queryVector))
.Take(topK)
.ToListAsync();
return results;
}
// 批量生成并存储嵌入
public async Task IndexAllProductsAsync()
{
var products = await _context.Products
.Where(p => p.Embedding == null)
.ToListAsync();
foreach (var product in products)
{
var text = $"{product.Name}: {product.Description}";
product.Embedding = await _embeddingService.GetEmbeddingAsync(text);
}
await _context.SaveChangesAsync();
}
}
ApproximateDistance vs 精确距离:
// 精确距离 — 全表扫描,O(n),适合小数据集
.OrderBy(p => p.Embedding!.Distance(queryVector))
// 近似向量搜索 — 使用 SQL Server 2025 的磁盘 ANN 索引,O(log n)
.OrderBy(p => p.Embedding!.ApproximateDistance(queryVector))
近似搜索利用 SQL Server 2025 的 DiskANN 索引,在百万级向量中也能毫秒级返回。精度略有损失(recall 通常在 95%+),但性能提升几个数量级。
5.2 JSON 映射不再是二等公民
// 之前:JSON 列映射在查询和迁移中有很多限制
// Preview 4:JSON 列完全融入关系模型
public class Order
{
public int Id { get; set; }
public string CustomerName { get; set; } = "";
// JSON 列 — 现在完全支持查询、跟踪、迁移
public ShippingAddress ShippingAddress { get; set; } = new();
public List<OrderItem> Items { get; set; } = [];
}
public class ShippingAddress
{
public string Street { get; set; } = "";
public string City { get; set; } = "";
public string ZipCode { get; set; } = "";
}
// 配置 JSON 列
modelBuilder.Entity<Order>(entity =>
{
entity.OwnsOne(o => o.ShippingAddress, builder =>
{
builder.ToJson(); // 存储为 JSON 列
builder.Property(a => a.City).HasMaxLength(100);
});
entity.OwnsMany(o => o.Items, builder =>
{
builder.ToJson();
builder.Property(i => i.ProductName).HasMaxLength(200);
});
});
// 查询 JSON 属性 — 现在完全支持
var ordersInShanghai = await _context.Orders
.Where(o => o.ShippingAddress.City == "上海")
.OrderByDescending(o => o.Items.Sum(i => i.Price * i.Quantity))
.ToListAsync();
// 迁移也自动处理 — JSON 列的变更会生成正确的迁移脚本
5.3 时态表周期属性映射
// SQL Server 时态表(Temporal Table)— 自动记录数据变更历史
public class Employee
{
public int Id { get; set; }
public string Name { get; set; } = "";
public decimal Salary { get; set; }
// 新增:显式映射时态表的周期列
public DateTime PeriodStart { get; set; }
public DateTime PeriodEnd { get; set; }
}
modelBuilder.Entity<Employee>(entity =>
{
entity.ToTable("Employees", tb => tb.IsTemporal());
// 现在可以在代码中访问周期属性
entity.Property(e => e.PeriodStart)
.HasColumnName("SysStartTime");
entity.Property(e => e.PeriodEnd)
.HasColumnName("SysEndTime");
});
// 查询历史数据
var salaryHistory = await _context.Employees
.TemporalFromTo(DateTime.Parse("2026-01-01"), DateTime.Parse("2026-05-01"))
.Where(e => e.Id == employeeId)
.OrderBy(e => e.PeriodStart)
.Select(e => new { e.Salary, e.PeriodStart, e.PeriodEnd })
.ToListAsync();
六、运行时与 JIT:性能的暗流
6.1 支持 1024+ CPU
这不是一个简单的常量修改。.NET 运行时内部使用 ProcInfo 结构体管理 CPU 亲和性(affinity),之前的设计假设 CPU 编号可以用 64 位掩码表示。支持 1024+ CPU 需要:
- 将 CPU 掩码从单个
ulong改为ulong[] - 修改线程池的分区逻辑
- 更新 GC 的堆分区策略
// 你可以在 1024+ 核心的机器上直接运行 .NET 了
// 对 Azure 超大实例、HPC 场景有直接意义
Environment.ProcessorCount; // 之前上限 64,现在可以返回 1024+
6.2 JIT 常量折叠 SequenceEqual
// 之前:运行时比较两个常量字符串
if ("hello".AsSpan().SequenceEqual("hello".AsSpan()))
{
// JIT 编译器会在编译时将这个比较直接替换为 true
// 不生成任何比较指令
}
// 更实用的场景:switch 表达式优化
var name = option switch
{
"enable" => Feature.Enable, // JIT 可以折叠这些常量比较
"disable" => Feature.Disable,
_ => Feature.Default
};
// 在 AOT 场景中,这些比较在编译时就确定了,运行时零开销
6.3 MemoryCache 内置 OpenTelemetry 指标
// 启用 MemoryCache 的 OpenTelemetry 指标
builder.Services.AddMemoryCache(options =>
{
options.TrackStatistics = true; // 启用统计跟踪
});
// 注册 OpenTelemetry
builder.Services.AddOpenTelemetry()
.WithMetrics(metrics => metrics
.AddMemoryCacheInstrumentation() // 自动采集缓存指标
.AddOtlpExporter());
// 可观测的指标:
// - cache.hit_rate: 缓存命中率
// - cache.eviction_count: 驱逐次数
// - cache.current_entry_count: 当前条目数
// - cache.current_size: 当前缓存大小
// 在 Grafana 中查看缓存性能
var hitRate = memoryCache.GetCurrentStatistics()?.TotalHitRate;
Console.WriteLine($"缓存命中率: {hitRate:P}");
七、Span-based 压缩 API:零分配的压缩解压
7.1 为什么 Span 这么重要
在 .NET 中,每次创建 byte[] 都是一次堆分配,这意味着 GC 压力。在高吞吐场景(网络中间件、流处理),这些中间字节数组的分配和回收会成为瓶颈。
// 旧方式:需要分配中间 byte[]
using var inputStream = new MemoryStream(inputData);
using var outputStream = new MemoryStream();
using var gzipStream = new GZipStream(inputStream, CompressionMode.Decompress);
await gzipStream.CopyToAsync(outputStream);
var result = outputStream.ToArray(); // 又一次分配
// 新方式:Span-based,零中间分配
Span<byte> output = stackalloc byte[maxOutputSize];
int bytesWritten = GZip.Decode(readonlyInputSpan, output);
// 或者使用租用数组池
byte[] rented = ArrayPool<byte>.Shared.Rent(maxOutputSize);
try
{
Span<byte> output = rented.AsSpan();
int bytesWritten = ZLib.Decode(readonlyInputSpan, output);
// 使用 output[..bytesWritten]
}
finally
{
ArrayPool<byte>.Shared.Return(rented);
}
7.2 实战:构建零分配的 HTTP 响应压缩中间件
public class ZeroAllocCompressionMiddleware
{
private readonly RequestDelegate _next;
public ZeroAllocCompressionMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var originalBody = context.Response.Body;
using var buffer = new PooledMemoryStream();
context.Response.Body = buffer;
await _next(context);
context.Response.Body = originalBody;
if (buffer.Length == 0) return;
var acceptEncoding = context.Request.Headers.AcceptEncoding.ToString();
if (!acceptEncoding.Contains("gzip") && !acceptEncoding.Contains("deflate"))
{
await buffer.CopyToAsync(originalBody);
return;
}
// 使用 Span-based 压缩
var inputSpan = buffer.GetBuffer().AsSpan(0, (int)buffer.Length);
Span<byte> compressed = stackalloc byte[(int)buffer.Length + 64]; // 额外空间用于压缩头
int compressedSize = acceptEncoding.Contains("gzip")
? GZip.Encode(inputSpan, compressed)
: Deflate.Encode(inputSpan, compressed);
context.Response.Headers.ContentEncoding = acceptEncoding.Contains("gzip") ? "gzip" : "deflate";
context.Response.Headers.ContentLength = compressedSize;
await originalBody.WriteAsync(compressed[..compressedSize]);
}
}
八、浮点数十六进制格式化:科学计算的刚需
// IEEE 754 十六进制格式
double pi = Math.PI;
string hex = pi.ToString("R"); // 传统十进制:3.141592653589793
string hexFloat = pi.ToHexString(); // 十六进制:0x1.921fb54442d18p+1
// 解析回来
double parsed = double.FromHexString("0x1.921fb54442d18p+1");
Console.WriteLine(parsed == pi); // True — 精确无损
// 为什么重要?
// 1. 精度保证:十进制表示可能丢失最后几位精度,十六进制不会
// 2. 二进制数据交换:不同平台间的精确浮点数传输
// 3. 调试:直接看到浮点数的二进制表示
// 实战:高精度科学计算中的结果对比
double computed = SimulatePhysics();
string stored = computed.ToHexString();
// 存入数据库或配置文件,读取时精确还原
double restored = double.FromHexString(stored);
Debug.Assert(computed == restored); // 精确匹配
九、SDK 与开发体验改进
9.1 dotnet watch 设备选择
# MAUI 开发者的福音
dotnet watch --device ios-simulator-iphone-15
dotnet watch --device android-emulator-pixel-8
# 列出可用设备
dotnet watch --list-devices
9.2 Fish Shell 补全
# 之前只有 Bash、Zsh、PowerShell 有补全
# 现在 Fish 用户也能享受 dotnet 命令补全了
dotnet b<TAB> # → build
dotnet run --<TAB> # → --configuration, --framework, --runtime, ...
9.3 dotnet reference 回退
# 之前:必须在项目目录下执行
cd MyProject
dotnet add reference ../OtherProject/OtherProject.csproj
# 现在:自动回退到当前目录的项目
dotnet add reference ../OtherProject/OtherProject.csproj
十、迁移指南:如何从 .NET 10 升级到 .NET 11 Preview 4
10.1 全局.json 配置
{
"sdk": {
"version": "11.0.100-preview.4.24267.8",
"rollForward": "latestPreview"
}
}
10.2 csproj 变更
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net11.0</TargetFramework>
<!-- 启用 Runtime-Async -->
<RuntimeAsync>on</RuntimeAsync>
<!-- 启用可空引用类型 -->
<Nullable>enable</Nullable>
<!-- 启用隐式 using -->
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
10.3 代码变更检查清单
- 搜索
IAsyncStateMachine的自定义实现 — 需要测试与 Runtime-Async 的兼容性 - 搜索对异步状态机字段的反射访问 — 如
<>1__state、<>u__1等,需要移除 - 搜索
Process类的直接使用 — 可以用新 API 简化 - 搜索
subprocess相关的第三方库 — 如CliWrap,评估是否可以用新 API 替代 - 检查 Blazor Server 的断线处理 — 可以用新的电路暂停机制
- 检查 EF Core 的 JSON 列使用 — 迁移到新的映射方式
10.4 兼容性矩阵
| 特性 | .NET 10 | .NET 11 Preview 4 | 说明 |
|---|---|---|---|
| Runtime-Async | 不支持 | 运行时库启用 | 自定义代码需手动开启 |
| Process 新 API | 不支持 | 支持 | 需 .NET 11 |
| MCP Server 模板 | 不支持 | 支持 | 需安装 .NET 11 SDK |
| EF Core 向量搜索 | 不支持 | 支持 | 需 SQL Server 2025 |
| 1024+ CPU | 不支持 | 支持 | 运行时级变更 |
| Blazor 电路暂停 | 不支持 | 支持 | 需 .NET 11 |
十一、性能基准:Preview 4 vs .NET 10
基于 TechEmpower 基准测试的初步数据(微软 .NET Blog 数据):
| 场景 | .NET 10 | .NET 11 P4 | 提升 |
|---|---|---|---|
| JSON 序列化 | 2.1M req/s | 2.3M req/s | +9.5% |
| 数据库查询 | 1.4M req/s | 1.5M req/s | +7.1% |
| 文件 I/O | 890K req/s | 980K req/s | +10.1% |
| 多层异步调用 | 650K req/s | 720K req/s | +10.8% |
注:以上数据来自微软官方博客,基于特定硬件配置,实际性能取决于具体场景。
Runtime-Async 在多层异步调用场景中的提升最为明显,这符合预期——异步层数越多,省掉的状态机开销越大。
十二、总结与展望
.NET 11 Preview 4 传递了三个关键信号:
1. 异步执行模型进入新纪元
Runtime-Async 的全面启用不是小修小补,而是 .NET 异步编程的范式转换。从编译器生成状态机到运行时原生调度,这个转变的影响会在 .NET 11 正式版及后续版本中持续放大。
2. 系统级编程体验补位完成
Process API 的扩展让 .NET 在系统编程领域不再"差一口气"。一行代码执行命令、流式处理输出、区分 stdout/stderr——这些在 Python/Go 中早已是标配的能力,.NET 终于也拥有了。
3. AI 原生开发成为一等公民
MCP Server 模板、EF Core 向量搜索、ASP.NET Core 的 AI 集成——微软在告诉开发者:.NET 不只是企业 CRUD 的工具,它是 AI 时代的全栈平台。
我的建议:
- 如果你在做微服务/后端开发:立即试用 Runtime-Async 和 Process API,评估性能收益
- 如果你在做 AI 应用:用 MCP Server 模板快速接入 AI Agent 生态
- 如果你在做 Blazor 应用:电路暂停/恢复功能可以让你的大规模部署成本降一个量级
- 如果你在做数据库相关:EF Core 的向量搜索和 JSON 映射改进值得升级
.NET 11 正式版预计 2026 年 11 月发布。从 Preview 4 的质量来看,这个版本值得等待。
本文基于 .NET Blog 官方发布说明及实际代码测试撰写。原文参见:https://devblogs.microsoft.com/dotnet/dotnet-11-preview-4/