Sdílet prostřednictvím


Vyřazení komponenty ASP.NET Core Razor

Poznámka:

Toto není nejnovější verze tohoto článku. Aktuální verzi najdete ve verzi .NET 10 tohoto článku.

Varování

Tato verze ASP.NET Core se už nepodporuje. Další informace najdete v zásadách podpory .NET a .NET Core. Aktuální vydání najdete v verzi .NET 9 tohoto článku.

Tento článek vysvětluje proces odstranění komponent ASP.NET Core Razor s IDisposable a IAsyncDisposable.

Pokud komponenta implementuje IDisposable nebo IAsyncDisposable, framework zajistí odstranění prostředků, když je komponenta odebrána z uživatelského rozhraní. Nespoléhejte na přesné načasování spuštění těchto metod. Například lze IAsyncDisposable aktivovat před tím, než je asynchronně očekáván Task v OnInitalizedAsync nebo OnParametersSetAsync, nebo poté, co je volán nebo dokončen. Kód pro odstranění objektů by také neměl předpokládat, že objekty vytvořené během inicializace nebo jiných metod životního cyklu existují.

Komponenty by se neměly implementovat IDisposable ani IAsyncDisposable současně. Pokud jsou oba implementované, architektura spustí pouze asynchronní přetížení.

Vývojářský kód musí zajistit, aby IAsyncDisposable implementace netrvaly příliš dlouho.

Další informace naleznete v úvodních poznámkách ASP.NET Core Blazor kontext synchronizace.

Vyřazení odkazů na objekty pro interoperabilitu JavaScriptu

Příklady v článcích o JavaScript (JS) interop ukazují typické vzory likvidace objektů.

JS Odkazy na objekty interop jsou implementovány jako mapa s klíčem tvořená identifikátorem na straně JS volání rozhraní, které vytvoří odkaz. Když je odstranění objektu z rozhraní .NET nebo JS inicializováno, Blazor odebere položku z mapy a objekt může být uvolněn (garbage collected), pokud na něj neexistuje žádný další silný odkaz.

Minimálně vždy odstraňte objekty vytvořené na straně .NET, aby nedošlo k úniku spravované paměti .NET.

Úlohy čištění DOM během odstraňování komponent

Další informace najdete v tématu ASP.NET Core Blazor JavaScript interoperabilita (JS interop).

Pokyny k JSDisconnectedException když je okruh odpojen, najdete v tématu Blazor Obecné pokyny ke zpracování chyb interoperability JavaScriptu najdete v části Interoperabilita JavaScriptu v části Řešení chyb v aplikacích ASP.NET Core.

Synchronní IDisposable

Pro synchronní úlohy odstraňování použijte IDisposable.Dispose.

Následující komponenta:

  • Implementuje IDisposable pomocí direktivy @implementsRazor.
  • Provádí likvidaci obj, což je typ, který implementuje IDisposable.
  • Provádí se kontrola hodnoty null, protože obj je vytvořen v metodě životního cyklu (není zobrazeno).
@implements IDisposable

...

@code {
    ...

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

Pokud jeden objekt vyžaduje odstranění, lambdu lze použít k uvolnění objektu při zavolání Dispose. Následující příklad se objeví v článku o vykreslování komponent ASP.NET Core a ukazuje použití lambda výrazu pro likvidaci Razor.

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

Poznámka:

V předchozím příkladu je volání na StateHasChanged obaleno voláním na ComponentBase.InvokeAsync, protože zpětné volání je vyvoláno mimo kontext synchronizace Blazor. Další informace najdete v tématu Vykreslování komponent ASP.NET Core Razor.

Pokud je objekt vytvořen v metodě životního cyklu, například OnInitialized{Async}, zkontrolujte null před voláním 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();
}

Další informace najdete tady:

Asynchronní IAsyncDisposable

Pro asynchronní úlohy likvidace použijte IAsyncDisposable.DisposeAsync.

Následující komponenta:

  • Implementuje IAsyncDisposable pomocí direktivy @implementsRazor.
  • Uvolňuje obj, což je nespravovaný typ implementující IAsyncDisposable.
  • Provádí se kontrola hodnoty null, protože obj je vytvořen v metodě životního cyklu (není zobrazeno).
@implements IAsyncDisposable

...

@code {
    ...

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

Další informace najdete tady:

Přiřazení null k vyřazeným objektům

Obvykle není nutné přiřazovat null k odstraněným objektům po voláníDispose/DisposeAsync . Mezi vzácné případy přiřazování null patří:

  • Pokud je typ objektu špatně implementován a netoleruje opakované volání Dispose/DisposeAsync, přiřaďte null po uvolnění, aby bylo možné elegantně přeskočit další volání na Dispose/DisposeAsync.
  • Pokud dlouhotrvající proces nadále uchovává odkaz na odstraněný objekt, přiřazení null umožňuje garbage collectoru uvolnit objekt, a to i navzdory dlouhodobému procesu, který na něj uchovává odkaz.

Jedná se o neobvyklé scénáře. Pro objekty, které jsou implementovány správně a chovají se normálně, neexistuje žádný bod přiřazování null k odstraněným objektům. Ve výjimečných případech, kdy musí být objekt přiřazen null, doporučujeme zdokumentovat důvod a vyhledat řešení, které brání nutnosti přiřazovat null.

StateHasChanged

Poznámka:

Volání StateHasChanged v Dispose a DisposeAsync není podporováno. StateHasChanged může být vyvolána jako součást odstraňování rendereru, takže vyžádání aktualizací uživatelského rozhraní v tomto okamžiku není podporováno.

Obslužné rutiny událostí

Vždy odstraňujte obslužné rutiny událostí z událostí .NET. Příklady formuláře Blazor ukazují, jak odregistrovat obslužnou rutinu události v metodě Dispose.

Přístup k privátnímu poli a k lambda funkcím:

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

Přístup soukromé metody:

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

Další informace o komponentách EditForm a formulářích najdete v tématu ASP.NET Blazor Přehled základních formulářů a další články formulářů v uzlu Formuláře .

Anonymní funkce, metody a výrazy

Pokud se používají anonymní funkce, metody nebo výrazy, není nutné implementovat IDisposable a odhlásit delegáty. Pokud se však nepodaří odhlásit delegáta, je problém , když objekt, který vystavuje událost, přesahuje životnost komponenty, která registruje delegáta. Pokud k tomu dojde, nastane únik paměti, protože registrovaný delegát zachová původní objekt v paměti. Proto používejte následující přístupy pouze v případě, že víte, že delegát události se rychle uvolní. Pokud máte pochybnosti o životnosti objektů, které vyžadují odstranění, použijte delegátskou metodu a správně uvolněte delegáta, jak ukazují dřívější příklady.

Použití anonymní lambda metody (není nutné explicitní uvolnění):

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

Přístup k anonymnímu výrazu lambda (explicitní odstranění se nevyžaduje):

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

Úplný příklad předchozího kódu s anonymními výrazy lambda je uveden v článku o ověřování formulářů v ASP.NET Core.

Další informace najdete v tématu Čištění nespravovaných prostředků a v následujících tématech k implementaci metod Dispose a DisposeAsync.

Vyřazení během JS spolupráce

Zachyťte JSDisconnectedException v potenciálních případech, kdy ztráta okruhu BlazorSignalR znemožňuje volání JS interoperabilní funkce a vede k neošetřené výjimce.

Další informace najdete v následujících zdrojích informací: