.NET Core 6中WPF通过SignalR接收MVC实时更新无响应问题
问题描述
我有一个WPF应用,点击按钮时会向ASP.NET Core 6 MVC应用发送请求。MVC控制器的动作方法会调用一个带循环的长任务方法,我想用SignalR实时更新WPF的标签,显示当前循环的迭代次数。但现在的问题是:点击按钮后,任务执行期间标签完全没变化,只有任务结束时才显示最后一次的迭代数值。我猜是同步/异步的问题,但作为新手搞不懂原理。
WPF按钮处理代码
private async void myButton_Click(object sender, RoutedEventArgs e) { HubConnection connection = new HubConnectionBuilder().WithUrl("https://localhost:7036/miHub").Build(); await connection.StartAsync(); connection.On<string>("Notify", (str) => { lblProgress.Content = str; }); //calls mvc controller action string url = "https://localhost:7036/Sincro/procesa"; using (var httpClient = new HttpClient()) { string json = await httpClient.GetStringAsync(url); } }
MVC控制器代码
public class SincroController : Controller { private readonly IHubContext<MiHub> _hubContext; //ctor public SincroController(IHubContext<MiHub> hubContext) { _hubContext = hubContext; } public async Task<JsonResult> procesa() { await longProcess(); var json = new { foo = "bar" }; return Json(json); } private async Task<int> longProcess() { for(int row=0; row<5000; row++) { await _hubContext.Clients.All.SendAsync("Notify", $"Processing thing {row}"); } return 0; } }
解决方案
核心问题分析
- WPF跨线程UI访问限制:WPF的UI控件只能由创建它的UI线程更新,SignalR的回调是在后台线程触发的,直接修改标签会被WPF阻止,导致界面无响应。
- MVC端消息积压:
longProcess循环里连续调用SendAsync但没有让出线程,ASP.NET Core会把消息缓存起来,直到整个任务完成才一次性推送,所以前端只能收到最后一条。
具体修复步骤
1. 修复WPF的UI线程更新问题
修改SignalR的回调逻辑,通过Dispatcher切换回UI线程更新标签:
connection.On<string>("Notify", (str) => { // 切换到UI线程执行更新操作 Application.Current.Dispatcher.Invoke(() => { lblProgress.Content = str; }); });
2. 解决MVC端的消息积压问题
在循环中添加短暂的延迟,让ASP.NET Core有机会及时推送已发送的SignalR消息:
private async Task<int> longProcess() { for(int row=0; row<5000; row++) { await _hubContext.Clients.All.SendAsync("Notify", $"Processing thing {row}"); // 让出线程,避免消息批量积压 await Task.Delay(1); } return 0; }
3. 优化SignalR连接生命周期(可选)
每次点击按钮创建新连接会浪费资源,建议把连接实例提升为类字段,提前初始化:
private HubConnection _hubConnection; public MainWindow() { InitializeComponent(); // 初始化SignalR连接 _hubConnection = new HubConnectionBuilder().WithUrl("https://localhost:7036/miHub").Build(); _hubConnection.On<string>("Notify", (str) => { Application.Current.Dispatcher.Invoke(() => { lblProgress.Content = str; }); }); // 提前启动连接,无需每次点击都重新建立 _ = _hubConnection.StartAsync(); } private async void myButton_Click(object sender, RoutedEventArgs e) { string url = "https://localhost:7036/Sincro/procesa"; using (var httpClient = new HttpClient()) { string json = await httpClient.GetStringAsync(url); } }
内容的提问来源于stack exchange,提问作者patsy2k
相关产品推荐
相关产品推荐

