Description
while developing a custom blazor component, some clientside object need to be cleared before the component being disposed. below sample is a simplified version taken from blazor-university:
https://blazor-university.com/javascript-interop/calling-dotnet-from-javascript/lifetimes-and-memory-leaks/
Dispose method on the page tries to invoke a js method to clear its clientside objects, then DotnetReference is disposed.
what we have noticed is the jsinterop call throws exception while page refresh/close, because jsruntime at that moment is already closed. this exception cause all objects on serverside from the closed circuit to stay in memory. (Take memory snapshot using diagnostic tool, and check ViewModel object, the viewmodel object created from previous circuits stays in memory after page refresh, and with jsinterop call removed from Dispose method, there is always just one Viewmodel in memory.
Configuration
refer this script block in _host.html
<script>
var BlazorUniversity = BlazorUniversity || {};
BlazorUniversity.startRandomGenerator = function(dotNetObject) {
setInterval(function () {
let text = Math.random() * 1000;
console.log("JS: Generated " + text);
dotNetObject.invokeMethodAsync('AddText', text.toString());
}, 1000);
return 1;
};
</script>
index.razor
@page "/"
@inject IJSRuntime JSRuntime
@implements IDisposable
<span>Text received</span>
<ul>
@foreach (string text in TextHistory)
{
<li>@text</li>
}
</ul>
@code
{
List<string> TextHistory = new List<string>();
int GeneratorHandle = -1;
DotNetObjectReference<Index> ObjectReference;
ViewModel model = new ViewModel() {Name = "Test person"};
protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);
if (firstRender)
{
ObjectReference = DotNetObjectReference.Create(this);
GeneratorHandle = await JSRuntime.InvokeAsync<int>("BlazorUniversity.startRandomGenerator", ObjectReference);
}
}
[JSInvokable("AddText")]
public void AddTextToTextHistory(string text)
{
TextHistory.Add(text.ToString());
while (TextHistory.Count > 10)
TextHistory.RemoveAt(0);
StateHasChanged();
System.Diagnostics.Debug.WriteLine("DotNet: Received " + text);
}
public async void Dispose()
{
if (GeneratorHandle != -1)
{
//Cancel our callback before disposing our object reference, this call cause exception in page close/refresh and block the GC
await JSRuntime.InvokeVoidAsync("BlazorUniversity.stopRandomGenerator", GeneratorHandle);
}
if (ObjectReference != null)
{
//Now dispose our object reference so our component can be garbage collected
ObjectReference.Dispose();
}
}
public class ViewModel
{
public string Name { get; set; }
}
}
Regression?
I have also tried in .net 3.1 and got the same issue.
Other information
what is the correct way to dispose client and server side resources? if this is a razor component, the dispose is called from both partial render when the component is removed from render tree (in this case, page is still active so this jsinterop call works fine), or when page closed (in this case, jsinterop call throw exception, even with try catch, we still observe the locking of all the closed page's object locked by renderer root object.
Description
while developing a custom blazor component, some clientside object need to be cleared before the component being disposed. below sample is a simplified version taken from blazor-university:
https://blazor-university.com/javascript-interop/calling-dotnet-from-javascript/lifetimes-and-memory-leaks/
Dispose method on the page tries to invoke a js method to clear its clientside objects, then DotnetReference is disposed.
what we have noticed is the jsinterop call throws exception while page refresh/close, because jsruntime at that moment is already closed. this exception cause all objects on serverside from the closed circuit to stay in memory. (Take memory snapshot using diagnostic tool, and check ViewModel object, the viewmodel object created from previous circuits stays in memory after page refresh, and with jsinterop call removed from Dispose method, there is always just one Viewmodel in memory.
Configuration
refer this script block in _host.html
index.razor
Regression?
I have also tried in .net 3.1 and got the same issue.
Other information
what is the correct way to dispose client and server side resources? if this is a razor component, the dispose is called from both partial render when the component is removed from render tree (in this case, page is still active so this jsinterop call works fine), or when page closed (in this case, jsinterop call throw exception, even with try catch, we still observe the locking of all the closed page's object locked by renderer root object.