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

为何Razor组件中SayHello方法会覆盖Subscribe订阅行为?

Razor组件中SayHello方法与Rx订阅的冲突问题分析

我是Reactive Extensions(Rx)新手,搞不懂为什么Razor组件里的SayHello方法会和Subscribe订阅事件产生冲突,调试时出现了预期外的结果:在两台电脑上打开网站,点击按钮后,首先触发组件的SayHello方法,随后订阅处理程序执行了4次。我原本以为Subscribe会为两个组件实例分别设置message变量。

相关代码

Index.razor

@page "/"
@using ClassLibrary1
@inject Class1 _x;
@implements IDisposable

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<p>@message</p>

<button type="button" @onclick="SayHello" class="btn btn-primary">Say Hello</button>

@code {
    string message = "";
    private IDisposable? _subscription;

    void SayHello()
    {
        message = _x.SayHello();
    }

    protected override Task OnInitializedAsync()
    {
        _subscription = _x.Notifier.Subscribe(x => {
            message = x;
            InvokeAsync(StateHasChanged);
        });
        return base.OnInitializedAsync();
    }

    public void Dispose()
    {
        _subscription?.Dispose();
    }
}

Class1

using System.Reactive.Subjects;

namespace ClassLibrary1
{
    public class Class1
    {
        private Subject<string> _notifier;

        public IObservable<string> Notifier { get { return _notifier; } }

        public Class1()
        {
            _notifier = new Subject<string>();
        }

        public string SayHello()
        {
            _notifier.OnNext("someone called SayHello");
            return "Hello";
        }
    }
}

问题原因

1. 订阅未取消导致累积

组件在OnInitializedAsync中订阅了_x.Notifier,但没有在组件销毁时取消订阅。Blazor组件在页面刷新、导航离开时会被销毁,但如果不手动调用订阅的Dispose(),Subject会保留旧的订阅者。每次重新加载页面,新的组件实例又会添加一个新订阅,最终导致Subject的订阅者数量越来越多——你看到的4次执行,就是累积了4个未取消的订阅。

2. 执行顺序导致的"覆盖"假象

点击按钮时,SayHello方法先执行message = _x.SayHello();,而_x.SayHello()内部调用了_notifier.OnNext("someone called SayHello"),这会立即触发所有订阅者的回调,把message改成"someone called SayHello"。所以你看到的最终结果是订阅回调的内容,而非SayHello的返回值,看起来像是"覆盖"了订阅事件,实际是代码执行顺序的问题:同步调用OnNext会立即触发所有订阅者,覆盖了之前的赋值。

3. 服务生命周期的影响

如果Class1是注册为**单例(Singleton)服务,所有客户端会共享同一个Class1实例和内部的Subject。这意味着一个客户端点击按钮,所有客户端的订阅者都会收到通知。如果你的预期是每个客户端独立处理,应该将Class1注册为作用域(Scoped)**服务(Blazor Server中每个SignalR连接对应一个Scoped实例)。

解决方法

  • 取消订阅,避免累积:让组件实现IDisposable接口,保存订阅返回的IDisposable实例,在组件销毁时调用Dispose()取消订阅,代码如上述Index.razor所示。
  • 调整执行逻辑,避免冲突:如果希望SayHello的返回值和订阅通知共存,可以拆分message为两个变量,或在订阅回调中添加判断逻辑。
  • 配置正确的服务生命周期:在Program.cs中注册Class1时,根据需求选择生命周期:
    // Scoped:每个客户端独立实例
    builder.Services.AddScoped<Class1>();
    // 或 Singleton:所有客户端共享实例(根据业务需求选择)
    // builder.Services.AddSingleton<Class1>();
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 04:10:32