最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

c# - Blazor Server Singleton Observer Pattern Holding References After Page Refresh - Stack Overflow

programmeradmin3浏览0评论

I'm using a singleton service in a Blazor Server app to notify components when an order changes. The service follows an observer pattern, where pages register event handlers that get triggered when an update occurs.

public class OrderOnChangeService : IOrderOnChangeService
{
    public event Action<List<int>, Guid>? OnOrderRowChanged;

    public void NotifyOrderRowChanged(List<int> orderIds, Guid changeId)
    {
        OnOrderRowChanged?.Invoke(orderIds, changeId);
    }

    public int GetObserverCount()
    {
        return OnOrderRowChanged?.GetInvocationList().Length ?? 0;
    }
}

In my Blazor component (.razor.cs), I register a method with this service:

protected override async Task OnInitializedAsync()
{
    OrderOnChangeService.OnOrderRowChanged += HandleRowChanged;
    Console.WriteLine($"Registered: {OrderOnChangeService.GetObserverCount()} observers");
}

public void Dispose()
{
    OrderOnChangeService.OnOrderRowChanged -= HandleRowChanged;
    Console.WriteLine($"Disposed: {OrderOnChangeService.GetObserverCount()} observers");
}

private void HandleRowChanged(List<int> orderIds, Guid changeId)
{
    // Handle updates
}

When I refresh the page, the Blazor circuit is terminated, but since OrderOnChangeService is a singleton, it still holds references to handlers from the disposed page instance. This seems to cause a memory leak because the event references are never removed unless the Dispose method is explicitly called.

How can I ensure that event handlers are always removed when a Blazor page is refreshed, so that the singleton service doesn’t keep stale references?

I'm using a singleton service in a Blazor Server app to notify components when an order changes. The service follows an observer pattern, where pages register event handlers that get triggered when an update occurs.

public class OrderOnChangeService : IOrderOnChangeService
{
    public event Action<List<int>, Guid>? OnOrderRowChanged;

    public void NotifyOrderRowChanged(List<int> orderIds, Guid changeId)
    {
        OnOrderRowChanged?.Invoke(orderIds, changeId);
    }

    public int GetObserverCount()
    {
        return OnOrderRowChanged?.GetInvocationList().Length ?? 0;
    }
}

In my Blazor component (.razor.cs), I register a method with this service:

protected override async Task OnInitializedAsync()
{
    OrderOnChangeService.OnOrderRowChanged += HandleRowChanged;
    Console.WriteLine($"Registered: {OrderOnChangeService.GetObserverCount()} observers");
}

public void Dispose()
{
    OrderOnChangeService.OnOrderRowChanged -= HandleRowChanged;
    Console.WriteLine($"Disposed: {OrderOnChangeService.GetObserverCount()} observers");
}

private void HandleRowChanged(List<int> orderIds, Guid changeId)
{
    // Handle updates
}

When I refresh the page, the Blazor circuit is terminated, but since OrderOnChangeService is a singleton, it still holds references to handlers from the disposed page instance. This seems to cause a memory leak because the event references are never removed unless the Dispose method is explicitly called.

How can I ensure that event handlers are always removed when a Blazor page is refreshed, so that the singleton service doesn’t keep stale references?

Share Improve this question edited 2 days ago Zhi Lv 21.5k1 gold badge27 silver badges37 bronze badges asked Feb 15 at 17:58 AtomicallyBeyondAtomicallyBeyond 4582 silver badges17 bronze badges 2
  • As you have observed, you must dispose them. – MrC aka Shaun Curtis Commented Feb 15 at 20:37
  • But Dispose() won't be called if garbage collection doesn't get a chance to run because the singleton service is still holding references to the handlers. The page instance may be eligible for collection, but since the event subscription keeps it referenced, it won’t be freed until the event is unsubscribed. Is there a way to automatically detect when a circuit is closed and clean up the references? – AtomicallyBeyond Commented Feb 16 at 5:10
Add a comment  | 

2 Answers 2

Reset to default 1

As we're discussing Blazor Server, components are managed by the Renderer in the Blazor Hub session [on the server]. When the component goes "out of scope" i.e. is no longer part of the RenderTree, the Renderer manages it's disposal. If the component implements I{Async}Dispoable the Renderer calls the dispose method (and the handler gets de-registered) before it drops it's references to the component.

In the case of a browser crash/reset, the Blazor Hub detects a broken session and cleans up the session. This includes the Renderer and RenderTree. Any components implementing I{Async}Dispoable will be disposed correctly.

As long as you implement I{Async}Dispoable correctly, and de-register your event handler, the references in the singleton will be disposed correctly.

You can probably implement a scoped pass-through service which registers itself with the event handler and implements dispose correctly [I haven't tested this]. All components register with the scoped pass-through service. This will be disposed by the ServiceProvider when the scoped ServiceProvider is disposed. You still have the issue of memory leaks in the scoped service if you don't dispose correctly, but the problem is now scoped to the SPA session rather than the application.

In Blazor Server side, I have found that WeakReferenceMessenger from Community Toolkit is a lot more cleaner. It gives you a message channel for communication and completely decouples classes. Make sure you understand the implications on memory leaks on using WeakReferenceMessenger vs StrongReferenceMessenger as mentioned on the page.

In your situation, you can simply raise the event from the singleton

WeakReferenceMessenger.Default.Send((List<OrderId>,Guid ChangeId)) // or a class that encapsulates the change

And on the component side

    @implements IRecepient<(List<OrderId>,Guid)>
    
    protected override async Task OnInitializedAsync()
    {
     ... 
     WeakReferenceMessenger.Default.Register(this,(List<OrderId>,Guid)) // or a class that encapsulates the change
    }
            
    public void Receive((List<OrderId>,Guid ChangeId) change )
    {
     // process change
    }
发布评论

评论列表(0)

  1. 暂无评论