Blazor University (42)JavaScript 互操作 —— 生命周期和内存泄漏

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Blazor University (42)JavaScript 互操作 —— 生命周期和内存泄漏相关的知识,希望对你有一定的参考价值。

原文链接:https://blazor-university.com/javascript-interop/calling-dotnet-from-javascript/lifetimes-and-memory-leaks/

生命周期和内存泄漏

源代码[1]

如果我们运行我们在从 Javascript 调用 .NET 中创建的应用程序并检查浏览器控制台窗口,我们会看到当我们导航到另一个页面时,JavaScript 仍在回调我们的组件。更糟糕的是,如果我们查看 Visual Studio 输出窗口,我们会看到我们的组件仍在被调用并输出从 JavaScript 传递的值,这意味着我们的组件还没有被垃圾回收!

当我们创建一个 DotNetObjectReference 时,Blazor 将生成一个唯一 ID(WASM 为整数,服务器端为 GUID)并在当前 JSRuntime 中存储对我们对象的查找。这意味着除非我们正确处理我们的引用,否则我们的应用程序将会泄漏内存。

DotNetObjectReference 类实现了 IDisposable。要解决我们的内存泄漏问题,我们需要执行以下操作:

  • 我们的组件应该保留对我们创建的 DotNetObjectReference 的引用。

  • 我们的组件应该实现 IDisposable 并释放我们的 DotNetObjectReference

@page "/"
@inject IJSRuntime JSRuntime
@implements IDisposable

<h1>Text received</h1>
<ul>
  @foreach (string text in TextHistory)
  
    <li>@text</li>
  
</ul>

@code

  List<string> TextHistory = new List<string>();
  DotNetObjectReference<Index> ObjectReference;

  protected override async Task OnAfterRenderAsync(bool firstRender)
  
    await base.OnAfterRenderAsync(firstRender);
    if (firstRender)
    
      ObjectReference = DotNetObjectReference.Create(this);
      await JSRuntime.InvokeVoidAsync("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 void Dispose()
  
    GC.SuppressFinalize(this);

    if (ObjectReference != null)
    
      //Now dispose our object reference so our component can be garbage collected
      ObjectReference.Dispose();
    
  
  • 第 3 行

    告诉编译器我们希望我们的组件实现 IDisposable

  • 第 16 行

    我们现在保留对 DotNetObjectReference 的引用。

  • 第 21 行

    如果这是我们的第一次渲染,我们创建一个 DotNetObjectReference 并将其传递给我们的 JavaScript 方法,以便它可以在生成新的随机数时回调我们。

  • 第 45 行

    当我们的组件被释放时,我们在 DotNetObjectReference 上调用 Dispose()

如果你还记得我们的 JavaScript 警告,我们不能过早调用 JavaScript,所以我们只在 OnAfterRender* 事件中使用 JSRuntime,并且只有在 firstRendertrue 时才使用。如果组件永远不会被渲染(例如,如果在服务器端 Blazor 应用程序中预渲染),那么我们的 DotNetObjectReference 将永远不会被创建,所以我们应该只在它不为 null 的情况下处理它。

警告:避免在已处理的 .NET 引用上调用方法

如果我们现在运行我们的应用程序,我们将看到我们的组件不再从 JavaScript 接收随机数。但是,如果我们查看浏览器的控制台窗口,我们会看到每秒都会出现一个错误。

一旦我们的 DotNetObjectReference 被释放,它就会从 JSRuntime 中移除,从而允许我们的组件被垃圾回收——因此引用不再有效并且不应该被 JavaScript 使用。接下来,我们将调整我们的组件,使其取消 JavaScript setInterval,以便在我们的组件被销毁后不再执行它。

首先,我们需要更新我们的 JavaScript 以便它返回在我们执行 setInterval 时创建的句柄。然后我们需要添加一个附加函数,该函数将接受该句柄作为参数并取消间隔。

var BlazorUniversity = BlazorUniversity || ;
BlazorUniversity.startRandomGenerator = function (dotNetObject) 
  return setInterval(function () 
    let text = Math.random() * 1000;
    console.log("JS: Generated " + text);
    dotNetObject.invokeMethodAsync('AddText', text.toString());
  , 1000);
;
BlazorUniversity.stopRandomGenerator = function (handle) 
  clearInterval(handle);
;
  • 第 3 行

    setInteval 创建的句柄从启动随机数生成器的函数返回。

  • 第 9 行

    一个函数,它将接受我们创建的间隔句柄并将其传递给 JavaScript clearInterval 函数。

最后,我们需要我们的组件跟踪我们创建的 JavaScript 区间的句柄,并在我们的组件被释放时调用新的 stopRandomGenerator 函数。

@page "/"
@inject IJSRuntime JSRuntime
@implements IDisposable

<h1>Text received</h1>
<ul>
  @foreach (string text in TextHistory)
  
    <li>@text</li>
  
</ul>

@code

  List<string> TextHistory = new List<string>();
  int GeneratorHandle = -1;
  DotNetObjectReference<Index> ObjectReference;

  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()
  
    GC.SuppressFinalize(this);

    if (GeneratorHandle != -1)
    
      //Cancel our callback before disposing our object reference
      await JSRuntime.InvokeVoidAsync("BlazorUniversity.stopRandomGenerator", GeneratorHandle);
    
    if (ObjectReference != null)
    
      //Now dispose our object reference so our component can be garbage collected
      ObjectReference.Dispose();
    
  
  • 第 16 行

    我们创建一个成员来保存对从 JavaScript BlazorUniversity.startRandomGenerator 函数返回的区间的引用。

  • 第 25 行

    我们将返回的句柄存储在我们的新成员中。

  • 第 46 行

    如果已设置句柄,我们将调用新的 JavaScript BlazoUniversity.stopRandomGenerator 函数,传递我们的区间句柄,以便将其传递给 clearInterval

Interval 在我们的 DotNetObjectReference 被释放之前被取消,因此我们的 JavaScript 不会尝试使用无效的对象引用调用 .NET 对象上的方法。根据良好的做法,我们在尝试清除之前检查 GeneratorHandle 成员是否已设置,以防在执行 OnAfterRender* 方法之前处理组件。

参考资料

[1]

源代码: https://github.com/mrpmorris/blazor-university/tree/master/src/JavaScriptInterop/CallingDotNetFromJavaScriptLifetimes

以上是关于Blazor University (42)JavaScript 互操作 —— 生命周期和内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

Blazor University (31)表单 —— 验证

Blazor University (27)路由 —— 检测导航事件

Blazor University (26)路由 —— 通过代码导航

Blazor University (45)依赖注入 —— 将依赖项注入 Blazor 组件

Blazor University 组件 — 创建组件

Blazor University (36)组件库