Teilen über


Razor ASP.NET Kernkomponenten-Entsorgung

Hinweis

Dies ist nicht die neueste Version dieses Artikels. Die aktuelle Version finden Sie in der .NET 10-Version dieses Artikels.

Warnung

Diese Version von ASP.NET Core wird nicht mehr unterstützt. Weitere Informationen finden Sie in der .NET- und .NET Core-Supportrichtlinie. Informationen zum aktuellen Release finden Sie in der .NET 9-Version dieses Artikels.

In diesem Artikel wird der ASP.NET Razor Kernkomponenten-Entsorgungsprozess mit IDisposable und erläutert IAsyncDisposable.

Wenn eine Komponente eine Komponente implementiert IDisposable oder IAsyncDisposable, ruft das Framework die Ressourcenentsorgung auf, wenn die Komponente aus der Benutzeroberfläche entfernt wird. Verlassen Sie sich nicht auf den genauen Zeitpunkt, zu dem diese Methoden ausgeführt werden. So kann zIAsyncDisposable. B. vor oder nach einem asynchronen TaskOnInitalizedAsync wartenden oder aufgerufenen oder abgeschlossenen Vorgang OnParametersSetAsync ausgelöst werden. Außerdem sollte objektentsorgungscode nicht davon ausgehen, dass Objekte, die während der Initialisierung oder anderen Lebenszyklusmethoden erstellt wurden, vorhanden sind.

Komponenten sollten nicht gleichzeitig implementiert und IDisposable gleichzeitig implementiert IAsyncDisposable werden müssen. Wenn beide implementiert sind, führt das Framework nur die asynchrone Überladung aus.

Entwicklercode muss sicherstellen, dass IAsyncDisposable Implementierungen nicht lange dauern.

Weitere Informationen finden Sie in den einleitenden Hinweisen ASP.NET Core-SynchronisierungskontextsBlazor.

Entsorgung von JavaScript-Interop-Objektverweise

Beispiele in den Interopartikeln von JavaScript (JS) veranschaulichen typische Objektentsorgungsmuster:

JS Interop-Objektverweise werden als Zuordnungsschlüssel eines Bezeichners auf der Seite des JS Interopaufrufs implementiert, der den Verweis erstellt. Wenn die Objektentsorgung entweder von .NET oder JS seitlich initiiert wird, Blazor wird der Eintrag aus der Karte entfernt, und das Objekt kann garbage collection werden, solange kein anderer starker Verweis auf das Objekt vorhanden ist.

Löschen Sie mindestens Objekte, die auf .NET-Seite erstellt wurden, um zu vermeiden, dass .NET verwalteter Arbeitsspeicher verloren geht.

DOM-Bereinigungsaufgaben während der Komponentenentsorgung

Weitere Informationen finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS Interop).

Anleitungen dazu JSDisconnectedException , wann ein Schaltkreis getrennt wird, finden Sie unter ASP.NET Core Blazor JavaScript-Interoperabilität (JS Interoperabilität). Allgemeine Anleitungen zur Interop-Fehlerbehandlung in JavaScript finden Sie im Abschnitt "JavaScript-Interoperabilität" in "Behandeln von Fehlern in ASP.NET Core-AppsBlazor".

Synchron IDisposable

Verwenden Sie IDisposable.Disposefür synchrone Entsorgungsaufgaben .

Die folgende -Komponente:

  • IDisposable Implementiert mit der @implementsRazor Direktive.
  • Gibt obj frei, das ein Typ ist, der IDisposable implementiert.
  • Es wird eine NULL-Überprüfung durchgeführt, da obj sie in einer Lebenszyklusmethode erstellt wird (nicht angezeigt).
@implements IDisposable

...

@code {
    ...

    public void Dispose()
    {
        obj?.Dispose();
    }
}

Wenn ein einzelnes Objekt die Entsorgung erfordert, kann eine Lambda-Funktion zum Löschen des Objekts verwendet werden, wenn Dispose das Objekt aufgerufen wird. Das folgende Beispiel wird im ASP.NET Core-Komponentenrenderingartikel Razor angezeigt und veranschaulicht die Verwendung eines Lambda-Ausdrucks für die Entsorgung eines 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();
}

Hinweis

Im vorherigen Beispiel wird der Aufruf StateHasChanged von einem Aufruf ComponentBase.InvokeAsync umschlossen, da der Rückruf außerhalb des BlazorSynchronisierungskontexts aufgerufen wird. Weitere Informationen finden Sie unter ASP.NET Core-KomponentenrenderingRazor.

Wenn das Objekt in einer Lebenszyklusmethode erstellt wird, zOnInitialized{Async}. B. , überprüfen Sie vor dem Aufrufen nullauf 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();
}

Weitere Informationen finden Sie unter:

Asynchron IAsyncDisposable

Verwenden Sie IAsyncDisposable.DisposeAsyncfür asynchrone Entsorgungsaufgaben .

Die folgende -Komponente:

  • IAsyncDisposable Implementiert mit der @implementsRazor Direktive.
  • Entsorgt obj, das ein nicht verwalteter Typ ist, der IAsyncDisposable implementiert.
  • Es wird eine NULL-Überprüfung durchgeführt, da obj sie in einer Lebenszyklusmethode erstellt wird (nicht angezeigt).
@implements IAsyncDisposable

...

@code {
    ...

    public async ValueTask DisposeAsync()
    {
        if (obj is not null)
        {
            await obj.DisposeAsync();
        }
    }
}

Weitere Informationen finden Sie unter:

Zuordnung von null verworfenen Objekten

Normalerweise ist es nicht erforderlich, verworfene Objekte nach dem Aufrufen nullDispose/zuzuweisen.DisposeAsync Zu den seltenen null Fällen für die Zuweisung gehören:

  • Wenn der Typ des Objekts schlecht implementiert ist und wiederholte Aufrufe Dispose/DisposeAsyncnicht toleriert, weisen Sie null nach der Entsorgung zu, um weitere Aufrufe Dispose/DisposeAsyncordnungsgemäß zu überspringen.
  • Wenn ein langlebiger Prozess weiterhin einen Verweis auf ein verworfenes Objekt enthält, ermöglicht das Zuweisen null des Garbage Collector das Freigeben des Objekts trotz des langlebigen Prozesses, der einen Verweis darauf enthält.

Dies sind ungewöhnliche Szenarien. Bei Objekten, die ordnungsgemäß implementiert sind und sich normal verhalten, gibt es keinen Punkt beim Zuweisen null zu verworfenen Objekten. In den seltenen Fällen, in denen ein Objekt zugewiesen nullwerden muss, empfehlen wir, den Grund zu dokumentieren und eine Lösung zu suchen, die verhindert, dass die Zuweisung nullerforderlich ist.

StateHasChanged

Hinweis

Anrufe StateHasChanged werden DisposeDisposeAsync nicht unterstützt. StateHasChanged kann als Teil des Abreißens des Renderers aufgerufen werden, sodass das Anfordern von UI-Updates zu diesem Zeitpunkt nicht unterstützt wird.

Ereignishandler

Gekündigte Ereignishandler immer von .NET-Ereignissen. Die folgenden Blazor Formularbeispiele zeigen, wie Sie einen Ereignishandler in der Dispose Methode kündigen.

Privater Feld- und Lambda-Ansatz:

@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;
    }
}

Ansatz für private Methoden:

@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;
    }
}

Weitere Informationen zu Komponenten EditForm und Formularen finden Sie unter ASP.NET Blazor Übersicht über Kernformulare und die anderen Formularartikel im Formularknoten .

Anonyme Funktionen, Methoden und Ausdrücke

Wenn anonyme Funktionen, Methoden oder Ausdrücke verwendet werden, ist es nicht erforderlich, Stellvertretungen zu implementieren IDisposable und abzubestellen. Wenn ein Delegat jedoch nicht abbestellt wird, tritt ein Problem auf, wenn das Objekt, das das Ereignis verfügbar macht, die Lebensdauer der Komponente, die den Delegaten registriert, überlebt. Wenn dies geschieht, ergibt sich ein Speicherverlust, da die registrierte Stellvertretung das ursprüngliche Objekt lebendig hält. Verwenden Sie daher nur die folgenden Ansätze, wenn Sie wissen, dass der Ereignisdelegat schnell verworfen wird. Wenn Zweifel an der Lebensdauer von Objekten bestehen, die zur Entsorgung erforderlich sind, abonnieren Sie eine Delegatmethode, und löschen Sie den Delegaten ordnungsgemäß, wie in den vorherigen Beispielen gezeigt.

Anonymer Lambda-Methodenansatz (explizite Entsorgung nicht erforderlich):

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);
}

Anonymer Lambda-Ausdrucksansatz (explizite Entsorgung nicht erforderlich):

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);
}

Das vollständige Beispiel des vorangehenden Codes mit anonymen Lambda-Ausdrücken wird im ASP.NET Core Blazor Forms Validation Artikel angezeigt.

Weitere Informationen finden Sie unter Bereinigen von nicht verwalteten Ressourcen und den Themen, die sie bei der Implementierung der Dispose Methoden befolgen DisposeAsync .

Entsorgung während der JS Interoperabilität

Fallen Sie JSDisconnectedException in potenziellen Fällen, in denen der Verlust von Blazor" SignalR Schaltungen" Interopaufrufe verhindert JS und zu einer unbehandelten Ausnahme führt.

Weitere Informationen finden Sie in den folgenden Ressourcen: