備註
這不是本文的最新版本。 關於目前版本,請參閱 本文的 .NET 10 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支持原則。 如需目前的版本,請參閱 本文的 .NET 9 版本。
本文說明使用 和 Razor的 IDisposable ASP.NET 核心IAsyncDisposable元件處置程式。
如果元件實作 IDisposable 或 IAsyncDisposable,則從 UI 移除元件時,架構會呼叫資源處置。 請勿依賴執行這些方法的確切時機。 例如,在呼叫或完成 IAsyncDisposable 或 Task 等候異步 OnInitalizedAsync 之前或之後,可以觸發 OnParametersSetAsync。 此外,物件處置程式碼不應該假設初始化期間建立的物件或其他生命週期方法存在。
元件不需要同時實作 IDisposable 和 IAsyncDisposable。 如果兩者皆實作,則架構只會執行非同步多載。
開發人員程式碼必須確保 IAsyncDisposable 實作不需花費過長的時間才能完成。
如需詳細資訊,請參閱 ASP.NET Core Blazor 同步處理內容的簡介備註。
JavaScript 互操作物件參考的釋放
JavaScript (JS) Interop 文章中的範例會示範典型物件處置模式:
從 .NET 呼叫 JS 時,如從 ASP.NET Core 中的 .NET 方法呼叫 JavaScript 函式 Blazor中所述,請處置從 .NET 或從 IJSObjectReference 所建立的任何 /IJSInProcessObjectReference/JSObjectReferenceJS,以免流失 JS 記憶體。
從 呼叫 JS.NET 時,如從 ASP.NET Core Blazor中的 JavaScript 函式呼叫 .NET 方法中所述,處置從 .NET 或 從 建立DotNetObjectReference的任何JS專案,以避免洩漏 .NET 記憶體。
JS Interop 物件參考會實作為一個以識別碼為索引鍵的對應表,由建立參考的 JS Interop 呼叫端負責實現。 當從 .NET 或 JS 端進行物件處置時,Blazor 會從對應中移除該項目,只要物件沒有其他強式參考存在,就可以對物件進行垃圾回收。
至少,務必處置在 .NET 端建立的物件,以免 .NET 受控記憶體洩漏。
元件銷毀時的 DOM 清理任務
如需詳細資訊,請參閱 ASP.NET Core Blazor JavaScript 互通性 (JS Interop)。
如需線路中斷連線時 JSDisconnectedException 的指導,請參閱 ASP.NET CoreBlazor JavaScript 互通性 (JS Interop)。 如需一般 JavaScript 互通性錯誤處理指導,請參閱 內的 Blazor 一節。
同步 IDisposable
針對同步處置工作,請使用 IDisposable.Dispose。
下列元件:
- 使用 IDisposable
@implements指令來實作 Razor。 - 釋放
obj,這是一個實作 IDisposable 的類型。 - 因為
obj是在生命週期方法(未顯示)中建立的,因此會執行 null 值檢查。
@implements IDisposable
...
@code {
...
public void Dispose()
{
obj?.Dispose();
}
}
如果單一物件需要處置,則呼叫 Dispose 時,可以使用 Lambda 來處置物件。 下列範例會出現在 ASP.NET CoreRazor 元件轉譯一文中,並示範如何使用 Lambda 運算式來處置 Timer。
TimerDisposal1.razor:
@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable
<PageTitle>Timer Disposal 1</PageTitle>
<h1>Timer Disposal Example 1</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
TimerDisposal1.razor:
@page "/timer-disposal-1"
@using System.Timers
@implements IDisposable
<PageTitle>Timer Disposal 1</PageTitle>
<h1>Timer Disposal Example 1</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
CounterWithTimerDisposal1.razor:
@page "/counter-with-timer-disposal-1"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer = new Timer(1000);
protected override void OnInitialized()
{
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer.Dispose();
}
備註
在上述範例中,對 StateHasChanged 的呼叫會被對 ComponentBase.InvokeAsync 的呼叫包裝,因為回呼是在 Blazor 的同步上下文之外被調用。 如需詳細資訊,請參閱 ASP.NET Core Razor 元件轉譯。
如果在生命週期方法中建立物件 (例如 OnInitialized{Async}),則會在呼叫 null 之前先檢查 Dispose。
TimerDisposal2.razor:
@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable
<PageTitle>Timer Disposal 2</PageTitle>
<h1>Timer Disposal Example 2</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
TimerDisposal2.razor:
@page "/timer-disposal-2"
@using System.Timers
@implements IDisposable
<PageTitle>Timer Disposal 2</PageTitle>
<h1>Timer Disposal Example 2</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer? timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
CounterWithTimerDisposal2.razor:
@page "/counter-with-timer-disposal-2"
@using System.Timers
@implements IDisposable
<h1>Counter with <code>Timer</code> disposal</h1>
<p>Current count: @currentCount</p>
@code {
private int currentCount = 0;
private Timer timer;
protected override void OnInitialized()
{
timer = new Timer(1000);
timer.Elapsed += (sender, eventArgs) => OnTimerCallback();
timer.Start();
}
private void OnTimerCallback()
{
_ = InvokeAsync(() =>
{
currentCount++;
StateHasChanged();
});
}
public void Dispose() => timer?.Dispose();
}
如需詳細資訊,請參閱:
非同步 IAsyncDisposable
針對非同步處置工作,請使用 IAsyncDisposable.DisposeAsync。
下列元件:
- 使用 IAsyncDisposable
@implements指令來實作 Razor。 - 處置
obj,這是實現 IAsyncDisposable 的非管理類型。 - 因為
obj是在生命週期方法(未顯示)中建立的,因此會執行 null 值檢查。
@implements IAsyncDisposable
...
@code {
...
public async ValueTask DisposeAsync()
{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}
如需詳細資訊,請參閱:
將 null 指派給已處理的物件
呼叫 nullDispose/ 之後,通常不需要指派 DisposeAsync 給已處置的物件。 指派 null 的罕見案例包括:
- 如果物件類型實作不佳,且不容許對 Dispose/DisposeAsync 重複呼叫,請在處置之後指派
null,以順利略過對 Dispose/DisposeAsync 的進一步呼叫。 - 如果持續執行的程序繼續保存已處置物件的參考,則指派
null可讓 垃圾回收器 釋放該物件,即使持續執行的程序仍然持有該物件的參考。
這些是不尋常的案例。 對於正確實作且正常運作的物件,將 null 指派給已處置的物件沒有意義。 在必須將 null 指派給物件的罕見情況下,建議您記錄原因,並尋求不須指派 null 的解決方案。
StateHasChanged
備註
在 StateHasChanged 和 Dispose 中呼叫 DisposeAsync 是不支援的。
StateHasChanged 可能會在卸載轉譯器時叫用,因此不支援在該時間點要求 UI 更新。
事件處理常式
務必從 .NET 事件中解除訂閱事件處理常式。 下列 Blazor 表單 範例示範如何在 Dispose 方法中取消訂閱事件處理程式。
私有欄位和 Lambda 表達式方法:
@implements IDisposable
<EditForm ... EditContext="editContext" ...>
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
...
private EventHandler<FieldChangedEventArgs>? fieldChanged;
protected override void OnInitialized()
{
editContext = new(model);
fieldChanged = (_, __) =>
{
...
};
editContext.OnFieldChanged += fieldChanged;
}
public void Dispose()
{
editContext.OnFieldChanged -= fieldChanged;
}
}
私有方法法則:
@implements IDisposable
<EditForm ... EditContext="editContext" ...>
...
<button type="submit" disabled="@formInvalid">Submit</button>
</EditForm>
@code {
...
protected override void OnInitialized()
{
editContext = new(model);
editContext.OnFieldChanged += HandleFieldChanged;
}
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
...
}
public void Dispose()
{
editContext.OnFieldChanged -= HandleFieldChanged;
}
}
如需 EditForm 元件和表單的詳細資訊,請參閱 ASP.NET Core Blazor 表單概觀和 Forms 節點中的其他表單文章。
匿名函式、方法和運算式
使用匿名函式、方法或運算式時,不需要實作 IDisposable 和取消訂閱委派。 不過,當公開事件物件超過註冊委派的元件存留期時,無法取消訂閱委派是個問題。 當發生這種情況時,會產生記憶體洩露,因為已註冊的委派讓原始物件保持存活。 因此,請只有在您知道事件委派會快速處置時,才使用下列方法。 當不確定需要處理的物件之存活時間時,請訂閱委派方法並確實地處理該委派,如前面的範例所示。
匿名 Lambda 方法 (不需要明確處置):
private void HandleFieldChanged(object sender, FieldChangedEventArgs e)
{
formInvalid = !editContext.Validate();
StateHasChanged();
}
protected override void OnInitialized()
{
editContext = new(starship);
editContext.OnFieldChanged += (s, e) => HandleFieldChanged((editContext)s, e);
}
匿名 Lambda 運算式方法 (不需要明確處置):
private ValidationMessageStore? messageStore;
[CascadingParameter]
private EditContext? CurrentEditContext { get; set; }
protected override void OnInitialized()
{
...
messageStore = new(CurrentEditContext);
CurrentEditContext.OnValidationRequested += (s, e) => messageStore.Clear();
CurrentEditContext.OnFieldChanged += (s, e) =>
messageStore.Clear(e.FieldIdentifier);
}
具有匿名 Lambda 運算式的上述程式碼完整範例會出現在 ASP.NET Core Blazor 表單驗證一文中。
如需詳細資訊,請參閱清除非受控資源及後續主題,這些主題說明如何實作Dispose和DisposeAsync方法。
Interop 期間的 JS 處置
可能發生JSDisconnectedException的情況是遺失Blazor的SignalR線路會阻止JS的互操作呼叫並導致未處理的異常。
如需詳細資訊,請參閱下列資源: