Blazor Server应用中async函数未真正异步并行执行的原因
问题根因
你遇到的问题和async/await本身没关系,核心是两个认知误区:
- 给方法加
async关键字不会自动把阻塞代码变成异步代码,async/await只是语法糖,只有你await的操作本身是真异步IO操作时,才不会阻塞线程 - Blazor Server的UI渲染是跑在专属同步上下文上的,如果你在事件处理里用同步方法阻塞了这个上下文,所有UI更新都会被卡住,直到阻塞结束才会批量执行
用你熟悉的PLC逻辑打个比方
同步执行就像你在PLC主循环里写了个阻塞等待指令:触发按钮之后,你先写了寄存器把下拉框禁用标志位置1,但是紧接着就写了个死等4秒读Ping返回的逻辑,整个主循环被卡在这4秒里,根本没空扫到输出寄存器的变化、把禁用状态下发到HMI,等4秒等待结束、Ping结果读到了,才会把之前的禁用状态和Ping结果一起输出,看起来就像禁用逻辑晚执行了。
真异步执行就像你置位禁用标志之后,立刻触发一次输出刷新把状态发到HMI,然后把Ping检测丢给后台通讯块去跑,主循环该干嘛干嘛,等通讯块返回Ping结果了,再触发一次输出刷新显示结果,等待的4秒里主循环不卡,HMI上立刻就能看到下拉框被禁用。
你代码里的具体错误
- 用了同步阻塞的
Ping.Send()方法:这个方法会直接占住当前线程等Ping返回,哪怕写在async标记的方法里,也不会让出线程,本质还是同步执行 - 状态修改后没有触发UI刷新:Blazor默认会在事件处理逻辑第一次
await真异步操作前,把状态改动攒着不推送到浏览器,你改完IsDisabled直接进了阻塞的Ping逻辑,线程被占死,根本没机会推送UI更新 - 把组件字段、方法全标了
static:静态成员是全局共享的,多个用户同时打开这个页面会出现状态串扰,属于额外的功能bug - 事件处理用了
async void签名:这种写法无法捕获异常,也无法正确被Blazor的事件系统跟踪执行状态
修正后的完整代码
@page "/connect" @using System.Net.NetworkInformation <select value="@selectedString_MAE" class="MAE" disabled="@IsDisabled" @onchange="func_MAE"> <option value="">-- Select MAE --</option> @foreach (var mae in templates_MAE) { <option value=@mae>@mae</option> } </select> <button class="btn btn-primary" @onclick="ConnectToMachine">Connect</button> <p>Ping: @Ping_Win_status</p> <p>Ping: @Ping_Win_response</p> @code { private bool IsDisabled { get; set; } private string selectedString_MAE; private string Ping_Win_status; private string Ping_Win_response; private List<string> templates_MAE = new(); // 原有下拉选项数据源 private void func_MAE(ChangeEventArgs e) { selectedString_MAE = e.Value?.ToString(); } private async Task ConnectToMachine() { // 第一步:立刻禁用下拉框 IsDisabled = true; // 主动通知Blazor立刻渲染UI改动,不需要等后续异步操作 StateHasChanged(); // 第二步:执行真异步Ping,等待过程不阻塞UI线程 await Ping_Test_Win(); } private async Task Ping_Test_Win() { Ping_Win_response = ""; Ping_Win_status = ""; try { Ping Ping_Win = new Ping(); // 用框架自带的真异步Ping方法,等待时不占线程 PingReply Reply_win = await Ping_Win.SendPingAsync(Connect2.MAE_IP_Win, 4000); if (Reply_win != null) { Ping_Win_response = $"Win {Reply_win.Status} Time(ms) : {Reply_win.RoundtripTime} Address : {Reply_win.Address}"; Ping_Win_status = Reply_win.Status.ToString(); if (Ping_Win_status == "Success") { // 后续连接逻辑写在此处 } } else { Ping_Win_response = "Win Null TimedOut"; Ping_Win_status = ""; } } catch (Exception ex) { // 错误日志逻辑 Ping_Win_response = $"Ping error: {ex.Message}"; } } }
修正后执行流程
- 点击Connect按钮后立刻进入
ConnectToMachine方法,先把IsDisabled设为true,调用StateHasChanged()强制触发UI渲染,浏览器端会立刻看到下拉框被禁用 - 之后执行
await Ping_Win.SendPingAsync(),这4秒等待时间里UI线程完全不被阻塞,页面保持响应 - Ping返回结果后,把结果写入绑定字段,Blazor自动触发二次渲染,显示最终Ping状态
内容的提问来源于stack exchange,提问作者Mdarende
相关产品推荐
相关产品推荐

