You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 10:12:40