Blazor Server 采用单连接顺序执行模型,同一用户交互复用组件实例并串行处理UI更新,多用户则独立并发;性能瓶颈主因是单次操作耗时过长、内存泄漏、SignalR配置不当及线程池饥饿。

Blazor Server 的连接模型决定了并发行为
Blazor Server 不是每个请求启动新线程或新实例,而是为每个客户端连接维持一个长期 SignalR 连接,并在该连接上下文中复用 ComponentBase 实例。这意味着:同一用户多次交互(如连续点击按钮)通常复用同一个组件实例;不同用户则对应不同连接和独立的组件生命周期。
关键点在于:RenderTreeRenderer 会按连接序列化地处理该连接上的所有 UI 更新(包括事件回调、StateHasChanged() 触发的重渲染),所以单个连接内不存在“并发渲染”——它是严格顺序执行的。
- 多个用户同时操作 → 多个独立连接 → 各自顺序执行,互不阻塞
- 同一用户快速连点两次 → 两个事件回调排队进入该连接的消息队列 → 第二个回调必须等第一个完全返回(含异步 await 完成)后才开始
-
async void在事件处理中是危险的,会导致框架失去等待时机,可能引发状态错乱或重复提交
常见性能瓶颈:UI 渲染卡顿与服务器资源耗尽
卡顿通常不是因为“并发太高”,而是单次操作耗时过长,阻塞了整个连接的消息循环。典型场景包括:同步 I/O(如 File.ReadAllText())、未限制的数据库查询、复杂对象深克隆、或在 OnInitializedAsync() 中执行未分页的全表加载。
更隐蔽的问题是内存泄漏:组件持有大对象(如 byte[]、缓存的 DataTable)且未在 Dispose() 中清理,随着连接数增长,服务器内存持续上涨。
- 避免在组件中缓存未受控的大型数据结构;改用服务层缓存 + 弱引用或 TTL 控制
- 所有异步操作必须使用
async Task,禁止async void - 对耗时操作加超时控制,例如:
await httpClient.GetAsync("api/data", cancellationToken).WaitAsync(TimeSpan.FromSeconds(5)); - 启用
ServerSideCaching仅适用于静态资源;Blazor Server 本身不缓存组件渲染结果
SignalR 配置直接影响并发承载能力
默认 SignalR 配置在高连接数下容易成为瓶颈。核心参数不是 CPU 或内存,而是 SignalR 的并发连接数限制和消息缓冲区大小。IIS、Kestrel 和 SignalR 自身都有独立的连接/吞吐限制。
1、对ASP内核代码进行DLL封装,从而大大提高了用户的访问速度和安全性;2、采用后台生成HTML网页的格式,使程序访问速度得到进一步的提升;3、用户可发展下级会员并在下级购买商品时获得差额利润;4、全新模板选择功能;5、后台增加磁盘绑定功能;6、后台增加库存查询功能;7、后台增加财务统计功能;8、后台面值类型批量设定;9、后台财务曲线报表显示;10、完善订单功能;11、对所有传输的字符串进行安全
例如,Kestrel 默认 MaxConcurrentConnections 为 null(无硬限),但 SignalR 的 MaximumReceiveMessageSize 默认仅 32 KB,上传大参数或 Base64 图片时会直接断连并抛出 BadHttpRequestException: Request body too large。
- 在
Program.cs中显式配置 SignalR 选项:builder.Services.AddSignalR(hubOptions => { hubOptions.MaximumReceiveMessageSize = 1024 * 1024; // 1 MB }); - Kestrel 需单独配置最大连接数和请求体大小:
builder.WebHost.ConfigureKestrel(serverOptions => { serverOptions.Limits.MaxConcurrentConnections = 10000; serverOptions.Limits.MaxRequestBodySize = 10 * 1024 * 1024; }); - IIS 用户需检查
web.config中的requestLimits和maxAllowedContentLength
诊断真实瓶颈:别只看 CPU
CPU 占用低但响应慢?大概率是线程池饥饿或 I/O 等待堆积。Blazor Server 的事件处理依赖 .NET 线程池调度,如果大量请求触发长时间同步阻塞(如 Thread.Sleep()、Task.Run(() => { /* CPU-bound */ }).Result),会迅速耗尽线程池,导致新消息无法及时调度。
用 dotnet-counters 监控关键指标:System.Runtime | ThreadPool Queue Length(持续 > 10 表示调度积压)、Microsoft.AspNetCore.Hosting | Requests In Progress(结合连接数判断是否某连接长期占用)。
- 不要用
.Result或.Wait(),它们会阻塞线程池线程 - CPU 密集型工作必须用
Task.Run()显式卸载,但要注意避免频繁创建短任务造成调度开销 - 生产环境务必启用 Application Insights 或 OpenTelemetry,记录
NavigationManager.NavigateTo()延迟、组件生命周期耗时、SignalR 消息往返时间
实际部署中,最常被忽略的是 SignalR 心跳超时与反向代理(如 Nginx、Azure Front Door)的空闲连接关闭策略不一致,导致连接静默断开却未触发 OnDisconnectedAsync,用户界面卡死无响应。这个链路层问题比代码逻辑更容易让整个并发模型失效。










