ASP.NET 核心 Razor 组件处置

注释

此版本不是本文的最新版本。 要查看当前版本,请参阅本文的.NET 9 版本

警告

此版本的 ASP.NET Core 不再受支持。 有关详细信息,请参阅 .NET 和 .NET Core 支持策略。 要查看当前版本,请参阅本文的.NET 9 版本

重要

此信息与预发布产品相关,相应产品在商业发布之前可能会进行重大修改。 Microsoft对此处提供的信息不作任何明示或暗示的保证。

要查看当前版本,请参阅本文的.NET 9 版本

本文介绍 ASP.NET 核心 Razor 组件处置过程 IDisposable 以及 IAsyncDisposable

如果组件实现 IDisposableIAsyncDisposable,框架在从 UI 中删除组件时会调用资源处置。 不要依赖执行这些方法的确切时间。 例如, IAsyncDisposable 可以在异步 Task 等待或 OnInitalizedAsync 调用或 OnParametersSetAsync 完成之前或之后触发。 此外,对象处置代码不应假定初始化期间创建的对象或其他生命周期方法存在。

组件不需要同时实现和IAsyncDisposable同时实现IDisposable。 如果两者都实现,则框架仅执行异步重载。

开发人员代码必须确保 IAsyncDisposable 实现不需要很长时间才能完成。

有关详细信息,请参阅 ASP.NET 核心 Blazor 同步上下文的介绍性说明。

JavaScript 互作对象引用的处置

JavaScript (JS) 互作文章中的示例演示典型的对象处置模式:

JS 互作对象引用作为映射实现,该映射由创建引用的互作调用的一侧的 JS 标识符进行键键。 从 .NET 或 JS 端启动对象处置时, Blazor 从映射中删除条目,只要不存在对对象的其他强引用,就可以垃圾回收对象。

至少,始终释放在 .NET 端创建的对象,以避免泄漏 .NET 托管内存。

组件处置期间 DOM 清理任务

有关详细信息,请参阅 ASP.NET Core BlazorJavaScript 互操作性(JS 互操作)

有关线路断开连接时的指南JSDisconnectedException,请参阅 ASP.NET Core Blazor JavaScript 互作性(JS互作)。 有关常规 JavaScript 互作错误处理指南,请参阅 ASP.NET Core Blazor 应用中处理错误的JavaScript 互作部分。

同步 IDisposable

对于同步处置任务,请使用 IDisposable.Dispose

以下 组件:

  • IDisposable使用@implementsRazor指令实现。
  • 释放 obj,这是实现 IDisposable的类型。
  • 执行 null 检查是因为 obj 是在生命周期方法(未显示)中创建的。
@implements IDisposable

...

@code {
    ...

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

如果单个对象需要处置,则调用时 Dispose 可以使用 lambda 释放对象。 以下示例显示在 ASP.NET Core Razor 组件呈现 文章中,并演示了如何使用 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 核心 Razor 组件呈现

如果在生命周期方法中创建对象,例如OnInitialized{Async},在调用Dispose之前进行检查null

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

以下 组件:

@implements IAsyncDisposable

...

@code {
    ...

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

有关详细信息,请参见:

null分配给已释放的对象

通常,无需在调用Dispose/DisposeAsync后分配给null已释放的对象。 分配的 null 罕见情况包括:

  • 如果对象的类型实现不佳且不能容忍重复调用 Dispose/DisposeAsync,请在处置后分配 null 以正常跳过对的 Dispose/DisposeAsync进一步调用。
  • 如果长期进程继续保留对已释放对象的引用,则分配 null 允许 垃圾回收器 释放该对象,尽管长期进程持有对它的引用。

这些都是不寻常的方案。 对于正确实现且行为正常的对象,没有分配给 null 已释放对象的点。 在必须分配 null对象的极少数情况下,我们建议记录原因并寻求阻止分配 null所需的解决方案。

StateHasChanged

注释

Dispose呼叫StateHasChangedDisposeAsync不受支持。 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 核心Blazor窗体概述和其他表单文章。

匿名函数、方法和表达式

使用 匿名函数、方法或表达式时,不需要实现 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 核心 Blazor 表单验证 文章中。

有关详细信息,请参阅 “清理非托管资源 ”及其后面的主题,了解如何实现 DisposeDisposeAsync 方法。

互作期间 JS 处置

JSDisconnectedException在丢失BlazorSignalR线路会阻止JS互作调用并导致未经处理的异常的情况下捕获陷阱。

有关详细信息,请参阅以下资源: