Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować się zalogować lub zmienić katalog.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Uwaga
Nie jest to najnowsza wersja tego artykułu. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.
Ostrzeżenie
Ta wersja ASP.NET Core nie jest już obsługiwana. Aby uzyskać więcej informacji, zobacz zasady pomocy technicznej platformy .NET i platformy .NET Core. Aby zapoznać się z aktualną wersją, zobacz artykuł w wersji .NET 10.
W tym artykule opisano proces usuwania składników ASP.NET Core Razor za pomocą elementów IDisposable i IAsyncDisposable.
Jeśli składnik implementuje IDisposable lub IAsyncDisposable, framework wywołuje proces usuwania zasobów po usunięciu składnika z interfejsu użytkownika. Nie polegaj na dokładnym czasie wykonywania tych metod. Na przykład IAsyncDisposable można wyzwolić przed wywołaniem lub zakończeniem asynchronicznego oczekiwania na Task w OnInitalizedAsync lub OnParametersSetAsync. Ponadto kod usuwania obiektów nie powinien zakładać, że istnieją obiekty utworzone podczas inicjowania lub innych metod cyklu życia.
Składniki nie powinny implementować IDisposable i IAsyncDisposable jednocześnie. Jeśli obie są implementowane, platforma wykonuje tylko przeciążenie asynchroniczne.
Kod programisty musi dbać o to, aby implementacje IAsyncDisposable nie trwały długo.
Aby uzyskać więcej informacji, zobacz uwagi wprowadzające ASP.NET Core Blazor kontekstu synchronizacji.
Usuwanie odwołań do obiektów międzyoperacyjnych języka JavaScript
Przykłady w artykułach dotyczących międzyoperacyjności JavaScript (JS) przedstawiają typowe wzorce zarządzania obiektami:
Podczas wywoływania JS z platformy .NET, zgodnie z opisem w temacie Wywoływanie funkcji JavaScript z metod platformy .NET w programie ASP.NET Core Blazor, należy zutylizować wszystkie utworzone obiekty zIJSObjectReference/IJSInProcessObjectReference/JSObjectReference bądź zJS, aby uniknąć wycieku pamięci JS.
Podczas wywoływania środowiska .NET z JSzgodnie z opisem w Wywołaj metody .NET z funkcji JavaScript w ASP.NET Core Blazornależy usunąć wszystkie utworzone DotNetObjectReference zarówno ze środowiska .NET, jak i z JS, aby uniknąć wycieku pamięci w .NET.
JS Odwołania do obiektów interop są implementowane jako mapa z kluczem identyfikatora po stronie wywołania międzyoperacyjnego JS , które tworzy odwołanie. Gdy usuwanie obiektu jest inicjowane z platformy .NET lub strony JS, Blazor usuwa wpis z mapy, a obiekt może zostać wykolekcjonowany przez odśmiecacz, o ile nie ma innego silnego odwołania do obiektu.
Co najmniej zawsze usuwaj obiekty utworzone po stronie platformy .NET, aby uniknąć wycieku pamięci zarządzanej platformy .NET.
Zadania czyszczenia DOM podczas usuwania komponentów
Aby uzyskać więcej informacji, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop).
Aby uzyskać wskazówki dotyczące JSDisconnectedException rozłączenia obwodu, zobacz ASP.NET Core Blazor JavaScript interoperability (JS interop). Aby uzyskać ogólne wskazówki dotyczące obsługi błędów międzyoperacyjności języka JavaScript, zobacz sekcję międzyoperacyjność języka JavaScript w Handle errors in ASP.NET Core Blazor apps.
Synchroniczny IDisposable
W przypadku zadań synchronicznego usuwania użyj polecenia IDisposable.Dispose.
Następujący składnik:
- Implementuje IDisposable za pomocą dyrektywy
@implementsRazor. - Usuwa element
obj, który jest typem implementującym IDisposable element. - Sprawdzenie wartości null jest wykonywane, ponieważ
objjest tworzone w metodzie cyklu życia aplikacji (nie pokazano).
@implements IDisposable
...
@code {
...
public void Dispose()
{
obj?.Dispose();
}
}
Jeśli pojedynczy obiekt wymaga usunięcia, można użyć funkcji lambda do usunięcia obiektu, gdy Dispose jest wywoływany. Poniższy przykład pojawia się w Razor ASP.NET Core i demonstruje użycie wyrażenia lambda do usuwania elementu 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();
}
Uwaga
W poprzednim przykładzie wywołanie metody StateHasChanged jest opakowane przez wywołanie metody ComponentBase.InvokeAsync, ponieważ wywołanie zwrotne jest wywoływane poza kontekstem synchronizacji Blazor. Aby uzyskać więcej informacji, zobacz Renderowanie składników platformy ASP.NET Core Razor.
Jeśli obiekt jest tworzony w metodzie cyklu życia, takiej jak OnInitialized{Async}, sprawdź null przed wywołaniem metody 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();
}
Aby uzyskać więcej informacji, zobacz:
- Czyszczenie zasobów niezarządzanych (dokumentacja platformy.NET)
- Operatory warunkowe o wartości null ?. i ? []
Asynchroniczny IAsyncDisposable
W przypadku zadań usuwania asynchronicznego użyj polecenia IAsyncDisposable.DisposeAsync.
Następujący składnik:
- Implementuje IAsyncDisposable za pomocą dyrektywy
@implementsRazor. - Usuwa
obj, który jest typem niezarządzanym implementującym IAsyncDisposable. - Sprawdzenie wartości null jest wykonywane, ponieważ
objjest tworzone w metodzie cyklu życia aplikacji (nie pokazano).
@implements IAsyncDisposable
...
@code {
...
public async ValueTask DisposeAsync()
{
if (obj is not null)
{
await obj.DisposeAsync();
}
}
}
Aby uzyskać więcej informacji, zobacz:
- kontekst synchronizacji ASP.NET Core Blazor
- Czyszczenie zasobów niezarządzanych (dokumentacja platformy.NET)
- Operatory warunkowe o wartości null ?. i ? []
Przypisanie null do obiektów zlikwidowanych
Zwykle nie ma potrzeby przypisywania null do obiektów usuniętych po wywołaniu metodyDispose/DisposeAsync . Rzadkie przypadki przypisywania null obejmują następujące elementy:
- Jeśli typ obiektu jest źle zaimplementowany i nie toleruje powtórzonych wywołań do Dispose/DisposeAsync metody, przypisz
nullpo jego usunięciu, aby w bezpieczny sposób pominąć dalsze wywołania do Dispose/DisposeAsync. - Jeśli długotrwały proces nadal przechowuje odwołanie do usuniętego obiektu, przypisanie
nullpozwala modułowi odśmiecania pamięci zwolnić obiekt pomimo długotrwałego procesu trzymającego odwołanie do niego.
Są to nietypowe scenariusze. W przypadku obiektów, które są implementowane prawidłowo i zachowują się normalnie, nie ma sensu przypisywania null do usuniętych obiektów. W rzadkich przypadkach, gdy należy przypisać obiekt null, zalecamy udokumentowanie przyczyny i szukanie rozwiązania, które uniemożliwia przypisanie obiektu null.
StateHasChanged
Uwaga
Wywoływanie StateHasChanged, Dispose i DisposeAsync nie jest obsługiwane.
StateHasChanged może być wywoływana w ramach usuwania modułu renderowania, więc żądanie aktualizacji interfejsu użytkownika w tym momencie nie jest obsługiwane.
Programy obsługi zdarzeń
Zawsze anuluj subskrypcje obsługi zdarzeń .NET. W poniższych przykładach Blazor formularza pokazano, jak anulować subskrypcję programu obsługi zdarzeń w metodzie Dispose.
Prywatne pole i podejście 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;
}
}
Metoda prywatna:
@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;
}
}
Aby uzyskać więcej informacji na temat składnika EditForm i formularzy, zobacz przegląd formularzy platformy ASP.NET Core Blazor, a także inne artykuły o formularzach w węźle Formularze.
Funkcje anonimowe, metody i wyrażenia
Gdy są używane funkcje anonimowe, metody lub wyrażenia, nie jest konieczne zaimplementowanie IDisposable i anulowanie subskrypcji delegatów. Jednak nieanulowanie subskrypcji delegata jest problemem , gdy obiekt ujawniający zdarzenie ma dłuższą żywotność niż element rejestrujący delegata. W takim przypadku następuje wyciek pamięci, ponieważ zarejestrowany delegat zachowuje oryginalny obiekt przy życiu. W związku z tym należy używać tylko następujących podejść, gdy wiadomo, że delegat zdarzenia zostaje zwolniony szybko. W przypadku wątpliwości co do cyklu życia obiektów, które wymagają usunięcia, należy zasubskrybować metodę delegata i prawidłowo zlikwidować delegata, jak pokazano we wcześniejszych przykładach.
Metoda anonimowej metody lambda (jawne usuwanie nie jest wymagane):
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);
}
Anonimowa metoda wyrażenia lambda (jawne usuwanie nie jest wymagane):
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);
}
Pełny przykład poprzedniego kodu z anonimowymi wyrażeniami lambda można znaleźć w artykule Blazor platformy ASP.NET Core .
Aby uzyskać więcej informacji, zobacz Oczyszczanie niezarządzanych zasobów oraz kolejne tematy dotyczące implementacji metod Dispose i DisposeAsync.
Podczas JS międzyoperacyjności usuwanie
Pułapka JSDisconnectedException w potencjalnych przypadkach, gdy utrata obwodu BlazorSignalR zapobiega JS wywołaniom międzyoperacyjnym i powoduje nieobsługiwany wyjątek.
Aby uzyskać więcej informacji, zobacz następujące zasoby:
ASP.NET Core