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

页面刷新后SignalR事件监听器是否未正确释放?

Blazor InteractiveServer模式下SignalR连接未正确释放的问题

问题场景

我有一个采用InteractiveServer渲染模式的Blazor页面组件,代码中创建了SignalR HubConnection并实现IAsyncDisposable接口。页面通过按钮发送消息,服务器端仅将消息转发给所有客户端。

首次打开页面点击按钮时,页面显示1次消息,Debug输出窗口也显示1次;但刷新页面后再次点击,页面仅显示1次消息,Debug窗口却显示2次——追踪电路可发现GetMessage被前后两个电路调用,仿佛前一次的事件监听器未注销。

我已在DisposeAsync中移除监听器、停止并释放HubConnection,但问题仍存在,想了解事件处理器是否未正确释放,以及背后的逻辑误区。

编辑补充:尝试相关方案后刷新页面的输出情况——Debug窗口显示两次"Hello",分别对应不同的电路ID。

组件代码

@page "/test"
@using System.Diagnostics
@using Microsoft.AspNetCore.SignalR.Client
@rendermode InteractiveServer

@implements IAsyncDisposable

<PageTitle>Test</PageTitle>

<button @onclick="SendMessage">Send Message</button>

@foreach(var m in _messages)
{
    <p>@m</p>
}

@code {
    private readonly List<string> _messages = [];
    private HubConnection? _hubConnection;

    protected override async Task OnInitializedAsync()
    {
        _hubConnection = new HubConnectionBuilder()
            .WithUrl("http://localhost:5200/message")
            .WithAutomaticReconnect()
            .Build();

        _hubConnection.On<string>("OnMessage", GetMessage);

        await _hubConnection.StartAsync();

        await base.OnInitializedAsync();
    }

    private void GetMessage(string message)
    {
        _messages.Add(message);
        Debug.WriteLine(message);
        InvokeAsync(StateHasChanged);
    }

    public async ValueTask DisposeAsync()
    {
        if (_hubConnection is not null)
        {
            _hubConnection.Remove("OnMessage");
            await _hubConnection.StopAsync();
            await _hubConnection.DisposeAsync();
        }
    }

    private async Task SendMessage()
    {
        await _hubConnection.SendAsync("SendMessage", "Hello");
    }
}

服务器端Hub代码

public async Task SendMessage(string message)
{
    await Clients.All.SendAsync("OnMessage", message);
}

问题分析与解决

核心原因

  1. 电路延迟回收:InteractiveServer模式下,Blazor组件实例绑定到电路,刷新页面会创建新电路,但旧电路的回收是异步且延迟的——旧组件实例可能仍存活,其SignalR连接的事件处理器还在接收消息。
  2. 事件处理器移除不精准:_hubConnection.Remove("OnMessage")仅按方法名移除,若底层委托引用匹配失败(比如委托被隐式包装),则无法正确移除旧的事件处理器。

修复方案

方案1:精准管理事件委托

注册时保存委托实例,移除时用该实例匹配,确保100%移除目标处理器:

@code {
    private readonly List<string> _messages = [];
    private HubConnection? _hubConnection;
    // 保存委托实例
    private Action<string>? _messageHandler;

    protected override async Task OnInitializedAsync()
    {
        _hubConnection = new HubConnectionBuilder()
            .WithUrl("http://localhost:5200/message")
            .WithAutomaticReconnect()
            .Build();

        _messageHandler = GetMessage;
        _hubConnection.On<string>("OnMessage", _messageHandler);

        await _hubConnection.StartAsync();
        await base.OnInitializedAsync();
    }

    private void GetMessage(string message)
    {
        _messages.Add(message);
        Debug.WriteLine(message);
        InvokeAsync(StateHasChanged);
    }

    public async ValueTask DisposeAsync()
    {
        if (_hubConnection is not null && _messageHandler is not null)
        {
            // 使用保存的委托实例移除
            _hubConnection.Remove("OnMessage", _messageHandler);
            await _hubConnection.StopAsync();
            await _hubConnection.DisposeAsync();
            _messageHandler = null;
            _hubConnection = null;
        }
    }

    private async Task SendMessage()
    {
        await _hubConnection.SendAsync("SendMessage", "Hello");
    }
}

方案2:缩短电路超时(按需调整)

通过配置缩短旧电路的保留时间,加速旧组件实例的回收:

// Program.cs
builder.Services.AddServerSideBlazor(options =>
{
    // 设置断开电路的保留时间为30秒(默认是5分钟)
    options.DisconnectedCircuitRetentionPeriod = TimeSpan.FromSeconds(30);
});

方案3:依赖注入管理HubConnection

将HubConnection注册为Scoped服务(每个电路一个实例),利用DI生命周期自动管理连接的创建与释放:

// Program.cs注册服务
builder.Services.AddScoped(sp =>
{
    var navigationManager = sp.GetRequiredService<NavigationManager>();
    return new HubConnectionBuilder()
        .WithUrl(navigationManager.ToAbsoluteUri("/message"))
        .WithAutomaticReconnect()
        .Build();
});

组件中注入使用:

@inject HubConnection HubConnection
@implements IAsyncDisposable

@code {
    private readonly List<string> _messages = [];
    private Action<string>? _messageHandler;

    protected override async Task OnInitializedAsync()
    {
        _messageHandler = GetMessage;
        HubConnection.On<string>("OnMessage", _messageHandler);
        
        if (HubConnection.State == HubConnectionState.Disconnected)
        {
            await HubConnection.StartAsync();
        }
        await base.OnInitializedAsync();
    }

    private void GetMessage(string message)
    {
        _messages.Add(message);
        Debug.WriteLine(message);
        InvokeAsync(StateHasChanged);
    }

    public async ValueTask DisposeAsync()
    {
        if (_messageHandler is not null)
        {
            HubConnection.Remove("OnMessage", _messageHandler);
            _messageHandler = null;
        }
    }

    private async Task SendMessage()
    {
        await HubConnection.SendAsync("SendMessage", "Hello");
    }
}

验证标准

修复后刷新页面,点击按钮时Debug窗口应仅显示一次消息,说明旧电路的事件处理器已被正确移除,旧连接已释放。

内容的提问来源于stack exchange,提问作者Rich

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 15:54:55