Condividi tramite


Dismissione dei componenti ASP.NET Core Razor

Nota

Questa non è la versione più recente di questo articolo. Per la versione corrente, vedere la versione .NET 10 di questo articolo.

Avvertimento

Questa versione di ASP.NET Core non è più supportata. Per altre informazioni, vedere i criteri di supporto di .NET e di.NET Core. Per la versione corrente, vedere la versione .NET 9 di questo articolo.

Questo articolo illustra il processo di eliminazione dei componenti di Razor base ASP.NET con IDisposable e IAsyncDisposable.

Se un componente implementa IDisposable o IAsyncDisposable, il framework chiama l'eliminazione delle risorse quando il componente viene rimosso dall'interfaccia utente. Non basarsi sulla tempistica esatta di quando questi metodi vengono eseguiti. Ad esempio, è possibile attivare IAsyncDisposable prima o dopo che un'operazione asincrona Task, attesa in OnInitalizedAsync o OnParametersSetAsync, venga chiamata o completata. Inoltre, il codice di eliminazione degli oggetti non deve presupporre che gli oggetti creati durante l'inizializzazione o altri metodi del ciclo di vita esistano.

I componenti non devono implementare IDisposable e IAsyncDisposable contemporaneamente. Se entrambi sono implementati, il framework esegue solo l'overload asincrono.

Il codice dello sviluppatore deve assicurarsi che il completamento delle implementazioni di IAsyncDisposable non richiede molto tempo.

Per altre informazioni, vedere le osservazioni introduttive di ASP.NET Core Blazor contesto di sincronizzazione.

Eliminazione dei riferimenti agli oggetti di interoperabilità JavaScript

Esempi negli articoli di interoperabilità JavaScript (JS) illustrano i tipici modelli di eliminazione degli oggetti.

JS riferimenti agli oggetti di interoperabilità vengono implementati come una mappa che utilizza come chiave un identificatore sul lato della chiamata di interoperabilità JS che crea il riferimento. Quando l'eliminazione di oggetti viene avviata dal lato .NET o JS, Blazor rimuove la voce dalla mappa e l'oggetto può essere sottoposto a garbage collection a condizione che non esistano altri riferimenti forti all'oggetto.

È necessario, come minimo, smaltire sempre gli oggetti creati sul lato .NET per evitare perdite nella memoria gestita di .NET.

Attività di pulizia DOM durante l'eliminazione dei componenti

Per altre informazioni, vedere ASP.NET Core Blazor Interoperabilità JavaScript (interoperabilitàJS).

Per indicazioni su JSDisconnectedException quando un circuito è disconnesso, vedere ASP.NET Core Blazor interoperabilità JavaScript (JS interop). Per indicazioni generali sulla gestione degli errori di interoperabilità JavaScript, vedere la sezione Interoperabilità JavaScript in Blazor.

IDisposable sincrono

Per le attività di eliminazione sincrone, usare IDisposable.Dispose.

Componente seguente:

  • Implementa IDisposable con la direttiva @implementsRazor.
  • Elimina obj, ovvero un tipo che implementa IDisposable.
  • Viene eseguito un controllo null perché obj viene creato in un metodo del ciclo di vita (non visualizzato).
@implements IDisposable

...

@code {
    ...

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

Se un singolo oggetto richiede l'eliminazione, è possibile usare un'espressione lambda per eliminare l'oggetto quando viene chiamato Dispose. L'esempio seguente appare nell'articolo del rendering dei componenti ASP.NET Core Razor e dimostra l'uso di un'espressione lambda per lo smaltimento di un 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();
}

Nota

Nell'esempio precedente, la chiamata a StateHasChanged è incapsulata da una chiamata a ComponentBase.InvokeAsync perché il callback è invocato al di fuori del contesto di sincronizzazione di Blazor. Per altre informazioni, vedere rendering dei componenti ASP.NET Core Razor.

Se l'oggetto viene creato in un metodo del ciclo di vita, ad esempio OnInitialized{Async}, verificare la presenza di null prima di chiamare 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();
}

Per altre informazioni, vedere:

IAsyncDisposable Asincrono

Per le attività di eliminazione asincrone, usare IAsyncDisposable.DisposeAsync.

Componente seguente:

  • Implementa IAsyncDisposable con la direttiva @implementsRazor.
  • Elimina obj, ovvero un tipo non gestito che implementa IAsyncDisposable.
  • Viene eseguito un controllo null perché obj viene creato in un metodo del ciclo di vita (non visualizzato).
@implements IAsyncDisposable

...

@code {
    ...

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

Per altre informazioni, vedere:

Assegnazione di null a oggetti deallocati

In genere, non è necessario assegnare null agli oggetti eliminati dopo aver chiamato Dispose/DisposeAsync. I rari casi per l'assegnazione di null includono quanto segue:

  • Se il tipo dell'oggetto non è implementato correttamente e non tollera chiamate ripetute a Dispose/DisposeAsync, assegnare null dopo l'eliminazione per ignorare normalmente altre chiamate a Dispose/DisposeAsync.
  • Se un processo di lunga durata continua a contenere un riferimento a un oggetto eliminato, l'assegnazione di null consente all'Garbage Collector di liberare l'oggetto nonostante il processo di lunga durata che contiene un riferimento.

Si tratta di scenari insoliti. Per gli oggetti implementati correttamente e si comportano normalmente, non è necessario assegnare null agli oggetti eliminati. Nei rari casi in cui è necessario assegnare un oggetto null, è consigliabile documentare il motivo e cercare una soluzione che impedisca la necessità di assegnare null.

StateHasChanged

Nota

La chiamata a StateHasChanged non è supportata in Dispose e DisposeAsync. StateHasChanged potrebbe essere richiamato come parte dell'disinstallazione del renderer, quindi la richiesta di aggiornamenti dell'interfaccia utente a quel punto non è supportata.

Gestori eventi

Ricordarsi sempre di annullare la sottoscrizione dei gestori eventi dagli eventi .NET. Nei seguenti esempi di modulo viene illustrato come rimuovere la sottoscrizione di un gestore eventi nel metodo Blazor.

Campo privato e approccio 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;
    }
}

Approccio al metodo privato:

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

Per altre informazioni sul componente e sui moduli di EditForm, vedere ASP.NET Panoramica dei moduli di base Blazor e gli altri articoli relativi ai moduli nel nodo Forms.

Funzioni, metodi ed espressioni anonime

Quando vengono usate funzioni anonime, metodi o espressioni, non è necessario implementare IDisposable e annullare la sottoscrizione dei delegati. Tuttavia, la mancata cancellazione dell'iscrizione di un delegato è un problema quando l'oggetto che espone l'evento supera la durata del componente che registra il delegato. In questo caso, si verifica una perdita di memoria perché il delegato registrato mantiene attivo l'oggetto originale. Pertanto, usare gli approcci seguenti solo quando si sa che il delegato dell'evento si libera rapidamente. In caso di dubbi sulla durata degli oggetti che richiedono l'eliminazione, iscriviti a un metodo delegato e correttamente smaltisci il delegato come illustrato negli esempi precedenti.

Approccio anonimo al metodo lambda (eliminazione esplicita non richiesta):

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

Approccio anonimo per l'espressione lambda (eliminazione esplicita non necessaria):

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

L'esempio completo del codice precedente con espressioni lambda anonime viene visualizzato nell'articolo Blazor ASP.NET Core .

Per altre informazioni, vedere Pulizia delle risorse non gestite e gli argomenti seguenti sull'implementazione dei metodi di Dispose e DisposeAsync.

Eliminazione durante l'interoperabilità JS

Trap JSDisconnectedException nei casi potenziali in cui la perdita del circuito Blazor di SignalRimpedisce le interoperabilità delle chiamate di JS e genera un'eccezione non gestita.

Per altre informazioni, vedere le risorse seguenti: