Blazor Server环境下Worker向组件推送实时上传进度通知的可行方案咨询
嘿,这个场景我刚好经手过好几次,Blazor Server里让后台Worker给组件实时推送上传进度,用SignalR完全是最优解——别担心,虽然你看到的资料大多偏向WASM,但适配到Server场景其实很顺畅,我给你一步步拆解实现细节:
核心思路
Blazor Server本身的底层通信就是基于SignalR的,所以我们可以直接复用这套机制,通过一个专门的SignalR Hub作为中间层:后台Worker通过IHubContext调用Hub的推送方法,Blazor组件则订阅Hub的进度更新事件,实现实时UI反馈。
具体实现步骤
1. 创建SignalR进度Hub
先建一个Hub类,用来处理客户端订阅和进度消息的转发,这里用分组机制来实现精准推送(比如每个上传任务对应一个分组,避免给所有用户推错进度):
using Microsoft.AspNetCore.SignalR; public class ProgressHub : Hub { // 让客户端订阅指定上传任务的进度更新 public async Task SubscribeToProgress(string uploadId) { await Groups.AddToGroupAsync(Context.ConnectionId, uploadId); } // 取消订阅 public async Task UnsubscribeFromProgress(string uploadId) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, uploadId); } }
2. 配置SignalR和Worker服务
在Program.cs里注册Hub和你的后台Worker:
// 注册SignalR服务 builder.Services.AddSignalR(); // 注册你的上传Worker(假设类名为UploadWorker) builder.Services.AddHostedService<UploadWorker>(); // 配置Hub端点 app.MapHub<ProgressHub>("/progressHub");
3. 改造Worker,注入IHubContext推送进度
在你的Worker里注入IHubContext<ProgressHub>,这样就能在上传过程中实时推送进度到对应的分组:
using Microsoft.AspNetCore.SignalR; public class UploadWorker : BackgroundService { private readonly IHubContext<ProgressHub> _hubContext; // 通过DI注入Hub上下文 public UploadWorker(IHubContext<ProgressHub> hubContext) { _hubContext = hubContext; } // 处理上传的核心方法,传入uploadId来标识任务 public async Task ProcessComponentUpload(string uploadId) { // 模拟上传耗时(替换成你的真实ERP上传逻辑) for (int progress = 0; progress <= 100; progress += 10) { await Task.Delay(1200); // 模拟每步耗时1.2秒 // 推送给该uploadId分组下的所有客户端 await _hubContext.Clients.Group(uploadId) .SendAsync("ReceiveProgressUpdate", progress); } } // Worker的后台执行逻辑(比如监听任务队列) protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { // 这里替换成你获取待处理上传任务的逻辑(比如从消息队列/数据库) var pendingTask = await GetPendingUploadTask(stoppingToken); if (pendingTask != null) { await ProcessComponentUpload(pendingTask.UploadId); } await Task.Delay(1000, stoppingToken); } } // 模拟获取任务的方法,根据你的实际场景替换 private async Task<UploadTask?> GetPendingUploadTask(CancellationToken token) { return await Task.FromResult(new UploadTask { UploadId = Guid.NewGuid().ToString() }); } } // 上传任务模型,根据你的需求扩展 public class UploadTask { public string UploadId { get; set; } = string.Empty; // 其他字段:比如组件列表数据、用户标识等 }
4. Blazor组件订阅进度,更新UI
在你的Razor组件里创建SignalR连接,订阅进度更新事件,并在UI上展示进度条:
@page "/component-upload" @inject IHubConnectionBuilder HubConnectionBuilder @inject NavigationManager NavManager @implements IAsyncDisposable <h3>组件列表上传至ERP系统</h3> @if (_hubConnection?.State == HubConnectionState.Connected) { <div class="progress mt-3" style="height: 30px;"> <div class="progress-bar progress-bar-striped progress-bar-animated" role="progressbar" style="width: @_progress%" aria-valuenow="@_progress" aria-valuemin="0" aria-valuemax="100"> @_progress% </div> </div> <button class="btn btn-primary mt-4" @onclick="StartUpload" disabled="@_isUploading"> @(_isUploading ? "上传中..." : "开始上传") </button> } else { <p class="text-muted">正在连接进度推送服务...</p> } @code { private HubConnection? _hubConnection; private int _progress; private bool _isUploading; private string? _currentUploadId; protected override async Task OnInitializedAsync() { // 初始化SignalR连接 _hubConnection = HubConnectionBuilder .WithUrl(NavManager.ToAbsoluteUri("/progressHub")) .Build(); // 订阅进度更新事件:注意必须用InvokeAsync更新UI,因为回调在后台线程 _hubConnection.On<int>("ReceiveProgressUpdate", (progress) => { _progress = progress; InvokeAsync(StateHasChanged); }); // 处理重连逻辑(可选但推荐) _hubConnection.Reconnecting += error => { Console.WriteLine($"正在重连:{error.Message}"); return Task.CompletedTask; }; _hubConnection.Reconnected += connectionId => { Console.WriteLine($"重连成功,新连接ID:{connectionId}"); // 重连后重新订阅当前上传任务的进度 if (!string.IsNullOrEmpty(_currentUploadId)) { _ = _hubConnection.SendAsync("SubscribeToProgress", _currentUploadId); } return Task.CompletedTask; }; try { await _hubConnection.StartAsync(); } catch (Exception ex) { Console.WriteLine($"连接失败:{ex.Message}"); // 这里可以添加用户友好的错误提示 } } private async Task StartUpload() { _isUploading = true; _progress = 0; _currentUploadId = Guid.NewGuid().ToString(); // 订阅当前上传任务的进度分组 if (_hubConnection != null) { await _hubConnection.SendAsync("SubscribeToProgress", _currentUploadId); } // 通知Worker开始处理这个上传任务(比如把任务加入队列) // 这里替换成你实际的任务触发逻辑,比如调用队列服务的Enqueue方法 // await _uploadQueueService.AddTask(new UploadTask { UploadId = _currentUploadId }); // 等待上传完成(可选,也可以让用户自由操作,进度实时更新) while (_progress < 100 && _isUploading) { await Task.Delay(500); } _isUploading = false; // 上传完成后取消订阅 if (_hubConnection != null && !string.IsNullOrEmpty(_currentUploadId)) { await _hubConnection.SendAsync("UnsubscribeFromProgress", _currentUploadId); } } // 组件销毁时清理SignalR连接 public async ValueTask DisposeAsync() { if (_hubConnection != null) { if (!string.IsNullOrEmpty(_currentUploadId)) { await _hubConnection.SendAsync("UnsubscribeFromProgress", _currentUploadId); } await _hubConnection.DisposeAsync(); } } }
关键注意事项
- 定向推送:用
uploadId分组是最优选择,既能避免给所有用户推送错误进度,也支持同一个用户同时发起多个上传任务。 - UI更新线程安全:SignalR的回调是在后台线程,必须调用
InvokeAsync(StateHasChanged)才能正确更新Blazor组件的UI。 - 重连处理:添加
Reconnected事件,确保连接断开重连后能重新订阅进度,避免丢失后续更新。 - 资源清理:组件销毁时必须取消订阅并释放SignalR连接,避免内存泄漏。
这个方案完全适配你的Blazor Server+后台Worker的场景,是官方推荐的实时通信方案,稳定性和扩展性都拉满。
内容的提问来源于stack exchange,提问作者mp9007
相关产品推荐
相关产品推荐

