2

I've a Blazor WebAssembly Hosted application in which I've the following component:

liveStreaming.razor

@if (_isStreaming) {
    <img src="@_streamUrl">
} else {
    // show loading circle
}

liveStreaming.razor.cs

using Microsoft.AspNetCore.Components;
using System;
using System.Threading.Tasks;
using System.Timers;

public partial class LiveStreaming: ComponentBase, IDisposable
{
    private bool _isStreaming;
    private string _streamUrl;
    private string _placeholderImgUrl;
    private Timer _checkConnectionTimer;

    protected override async Task OnInitializedAsync() {
        _isStreaming = false;
        _placeholderImgUrl = "emptyImage.jpg";
        _checkConnectionTimer = new Timer();
        _checkConnectionTimer.Interval = 6000;
        _checkConnectionTimer.Elapsed += CheckConnection;
        _checkConnectionTimer.Start();
        // [...]
    }

    private async void CheckConnection(object sender, ElapsedEventArgs e) {
        _checkConnectionTimer.Stop();
        if (IsConnectionEstablished()) {
            _isStreaming = true;
            _streamUrl = "http://192.168.0.2/axis-cgi/mjpg/video.cgi";
            StateHasChanged();
        } else {
            _isStreaming = false;
            StateHasChanged();
        }
        _checkConnectionTimer.Start();
    }

    public void Clean() {
        _checkConnectionTimer.Stop();
        _streamUrl = _placeholderImgUrl;
        StateHasChanged();
    }

    public async void Dispose() {
        if (_checkConnectionTimer != null) { _checkConnectionTimer.Dispose(); }
    }
}

The problem is that, without the Close() method, if I navigate to another component of the Blazor app, the stream request from the img tag is not interrupted despite the Dispose() method being called. I can see this from the bandwitdh usage. Furthermore, if I come back to the page, let's say the stream bandwitdh is 3Mb/s, another 3Mb/s is added to the currently used bandwidth. And this happens every time I leave and then come back to the page. It's like http stream request is never interrupted and a new one is created every time, without removing the old one.

In order to circumvent this problem I had to add the Clean() method you see. I've had to setup a complex mechanism in order to change the currently loaded component: every time a request to navigate to a different component arrives, the mechanism calls the Clean() method on the current loaded component before invoking _navigationManager.NavigateTo("OtherComponentName"). In other words Clean() is always called just before Dispose() method.

I'm not very happy with this solution since I've had to arrange a complex mechanism in order to achieve something that should be a given. Do you know a better way to do this?

Some test I've done:

  1. Moving the code that now lies in Clean() inside Dispose() does nothing. Even if after StateHasChanged() I invoke Task.Delay(1). I suppose once the Dispose method has been called, the component is not rendered anymore.

  2. Changing the code in Clean() to

    _checkConnectionTimer.Stop(); _isStreaming = false; StateHasChanged();

Does nothing. It's like I have to change the img src in order to force the http stream request to stop.

Any help will be greately appreciated. Thanks.

EDIT: I found out that in order to mitigate the problem, you can define a javascript method in charge of resetting the img 'src' attribute and invoking it in the Dispose() method. This way there is no need to use strange workarounds like the one I described above.

1 Answers1

0

You need to add a finalizer/deconstructor to properly call the dispose. There is a recent article that describes the proper way to create a disposable class:

EVERYTHING THAT EVERY .NET DEVELOPER NEEDS TO KNOW ABOUT DISPOSABLE TYPES: PROPERLY IMPLEMENTING THE IDISPOSABLE INTERFACE

Here is the full example code he provides in the post:

public class PdfStreamer : IDisposable
{
    private bool _disposed;
    private readonly MemoryStream _stream = new MemoryStream();

    public PdfStreamer()
    {}

    ~PdfStreamer() => Dispose();

    protected virtual void Dispose(bool disposing)
    {
        if (this._disposed)
        {
            return;
        }

        if (disposing)
        {
            this._stream?.Dispose();
        }

        this._disposed = true;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}
J-Rome
  • 155
  • 1
  • 8
  • Thank you for the answer (and sorry for the delay!). What you've posted is a good pratice on how to use Dispose() but does not address the problem I have: I've followed the structure you've posted and, I moved the part in which I change the source of the image in order to "remove" the stream request in the destructor. However, it seems the destructor is not called, and I've the original problem again (i.e. stream requests does not stop and instead stack). It seems you should not rely on destructor in Blazor https://stackoverflow.com/questions/18411720/why-is-the-destructor-not-being-called – Alessandro Martinelli Nov 17 '21 at 15:28