原文鏈接: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?collectedObjectReference.Dispose();}}
}
第 3 行
告訴編譯器我們希望我們的組件實現
IDisposable
。第 16 行
我們現在保留對
DotNetObjectReference
的引用。第 21 行
如果這是我們的第一次渲染,我們創建一個
DotNetObjectReference
并將其傳遞給我們的 JavaScript 方法,以便它可以在生成新的隨機數時回調我們。第 45 行
當我們的組件被釋放時,我們在
DotNetObjectReference
上調用Dispose()
。
如果你還記得我們的 JavaScript 警告,我們不能過早調用 JavaScript,所以我們只在 OnAfterRender*
事件中使用 JSRuntime
,并且只有在 firstRender
為 true
時才使用。如果組件永遠不會被渲染(例如,如果在服務器端 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?referenceawait?JSRuntime.InvokeVoidAsync("BlazorUniversity.stopRandomGenerator",?GeneratorHandle);}if?(ObjectReference?!=?null){//Now?dispose?our?object?reference?so?our?component?can?be?garbage?collectedObjectReference.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