Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions aspnetcore/blazor/components/lifecycle.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,8 @@ protected override async Task OnInitializedAsync()

It isn't necessary to call <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A?displayProperty=nameWithType> unless a custom base class is used with custom logic. For more information, see the [Base class lifecycle methods](#base-class-lifecycle-methods) section.

A component must ensure that it's in a valid state for rendering when <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> returns from awaiting a potentially incomplete <xref:System.Threading.Tasks.Task>. If the method returns an incomplete <xref:System.Threading.Tasks.Task>, the part of the method that completes synchronously must leave the component in a valid state for rendering. For more information, see the introductory remarks of <xref:blazor/components/sync-context> and [Component disposal with `IDisposable` and `IAsyncDisposable` (this article)](#component-disposal-with-idisposable-and-iasyncdisposable).

Blazor apps that prerender their content on the server call <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> *twice*:

* Once when the component is initially rendered statically as part of the page.
Expand All @@ -216,7 +218,7 @@ To prevent developer code in <xref:Microsoft.AspNetCore.Components.ComponentBase

While a Blazor app is prerendering, certain actions, such as calling into JavaScript (JS interop), aren't possible. Components may need to render differently when prerendered. For more information, see the [Prerendering with JavaScript interop](#prerendering-with-javascript-interop) section.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section.
If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` and `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section.

::: moniker range=">= aspnetcore-8.0"

Expand Down Expand Up @@ -316,7 +318,9 @@ protected override async Task OnParametersSetAsync()

It isn't necessary to call <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A?displayProperty=nameWithType> unless a custom base class is used with custom logic. For more information, see the [Base class lifecycle methods](#base-class-lifecycle-methods) section.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section.
If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` and `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section.

If a disposable component doesn't use a <xref:System.Threading.CancellationToken>, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSet%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> should check if the component is disposed. If <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> returns an incomplete <xref:System.Threading.Tasks.Task>, the component must ensure that the part of the method that completes synchronously leaves the component in a valid state for rendering. For more information, see the introductory remarks of <xref:blazor/components/sync-context> and [Component disposal with `IDisposable` and `IAsyncDisposable` (this article)](#component-disposal-with-idisposable-and-iasyncdisposable).

For more information on route parameters and constraints, see <xref:blazor/fundamentals/routing>.

Expand Down Expand Up @@ -422,7 +426,7 @@ Even if you return a <xref:System.Threading.Tasks.Task> from <xref:Microsoft.Asp
1. The component executes on the server to produce some static HTML markup in the HTTP response. During this phase, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> aren't called.
1. When the Blazor script (`blazor.{server|webassembly|web}.js`) starts in the browser, the component is restarted in an interactive rendering mode. After a component is restarted, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRender%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnAfterRenderAsync%2A> **are** called because the app isn't in the prerendering phase any longer.

If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section.
If event handlers are provided in developer code, unhook them on disposal. For more information, see the [Component disposal with `IDisposable` and `IAsyncDisposable`](#component-disposal-with-idisposable-and-iasyncdisposable) section.

## Base class lifecycle methods

Expand Down Expand Up @@ -763,12 +767,14 @@ Although the content in this section focuses on Blazor Server and stateful Signa

## Component disposal with `IDisposable` and `IAsyncDisposable`

If a component implements <xref:System.IDisposable> or <xref:System.IAsyncDisposable>, the framework calls for resource disposal when the component is removed from the UI. Don't rely on the exact timing of when these methods are executed. For example, <xref:System.IAsyncDisposable> can be triggered before or after an asynchronous <xref:System.Threading.Tasks.Task> awaited in [`OnInitalizedAsync`](#component-initialization-oninitializedasync) is called or completes. Also, object disposal code shouldn't assume that objects created during initialization or other lifecycle methods exist.
If a component implements <xref:System.IDisposable> or <xref:System.IAsyncDisposable>, the framework calls for resource disposal when the component is removed from the UI. Don't rely on the exact timing of when these methods are executed. For example, <xref:System.IAsyncDisposable> can be triggered before or after an asynchronous <xref:System.Threading.Tasks.Task> awaited in [`OnInitalizedAsync`](#component-initialization-oninitializedasync) or [`OnParametersSetAsync`](#after-parameters-are-set-onparameterssetasync) is called or completes. Also, object disposal code shouldn't assume that objects created during initialization or other lifecycle methods exist.

Components shouldn't need to implement <xref:System.IDisposable> and <xref:System.IAsyncDisposable> simultaneously. If both are implemented, the framework only executes the asynchronous overload.

Developer code must ensure that <xref:System.IAsyncDisposable> implementations don't take a long time to complete.

For more information, see the introductory remarks of <xref:blazor/components/sync-context>.

### Disposal of JavaScript interop object references

Examples throughout the [JavaScript (JS) interop articles](xref:blazor/js-interop/index) demonstrate typical object disposal patterns:
Expand Down Expand Up @@ -950,6 +956,7 @@ The following component:

For more information, see:

* <xref:blazor/components/sync-context>
* [Cleaning up unmanaged resources (.NET documentation)](/dotnet/standard/garbage-collection/unmanaged)
* [Null-conditional operators ?. and ?[]](/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-)

Expand Down
12 changes: 12 additions & 0 deletions aspnetcore/blazor/components/synchronization-context.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ Blazor uses a synchronization context (<xref:System.Threading.SynchronizationCon

Blazor's server-side synchronization context attempts to emulate a single-threaded environment so that it closely matches the WebAssembly model in the browser, which is single threaded. This emulation is scoped only to an individual circuit, meaning two different circuits can run in parallel. At any given point in time within a circuit, work is performed on exactly one thread, which yields the impression of a single logical thread. No two operations execute concurrently within the same circuit.

A single logical thread of execution doesn't imply a single asynchronous control flow. A component is re-entrant at any point where it awaits an incomplete <xref:System.Threading.Tasks.Task>. [Lifecycle methods](xref:blazor/components/lifecycle) or [component disposal methods](xref:blazor/components/lifecycle#component-disposal-with-idisposable-and-iasyncdisposable) may be called before the asynchronous control flow resumes after awaiting a <xref:System.Threading.Tasks.Task> to complete. Therefore, a component must ensure that it's in a valid state before awaiting a potentially incomplete <xref:System.Threading.Tasks.Task>. In particular, a component must ensure that it's in a valid state for rendering when <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> or <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> return. If either of these methods return an incomplete <xref:System.Threading.Tasks.Task>, they must ensure that the part of the method that completes synchronously leaves the component in a valid state for rendering.

Another implication of re-entrant components is that a method can't defer a <xref:System.Threading.Tasks.Task> until after the method returns by passing it to <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType>. Calling <xref:Microsoft.AspNetCore.Components.ComponentBase.InvokeAsync%2A?displayProperty=nameWithType> may only defer the <xref:System.Threading.Tasks.Task> until the next [`await` operator](/dotnet/csharp/language-reference/operators/await) is reached.

Components might implement <xref:System.IDisposable> or <xref:System.IAsyncDisposable> to call asynchronous methods using a <xref:System.Threading.CancellationToken> from a <xref:System.Threading.CancellationTokenSource> that's canceled when the component is disposed. However, this really depends on the scenario. It's up to the component author to determine whether that's the correct behavior. For example, if implementing a `SaveButton` component that persists some local data to a database when a save button is selected, the component author may indeed intend to discard the changes if the user selects the button and quickly navigates to another page, which could dispose the component before the asynchronous save completes.

A disposable component can check for disposal after awaiting any <xref:System.Threading.Tasks.Task> that doesn't receive the component's <xref:System.Threading.CancellationToken>. Incomplete <xref:System.Threading.Tasks.Task>s may also prevent garbage collection of a disposed component.

<xref:Microsoft.AspNetCore.Components.ComponentBase> ignores exceptions caused by <xref:System.Threading.Tasks.Task> cancellation (more precisely, it ignores *all* exceptions if the awaited <xref:System.Threading.Tasks.Task>s are canceled), so component methods don't need to handle <xref:System.Threading.Tasks.TaskCanceledException> and <xref:System.OperationCanceledException>.

<xref:Microsoft.AspNetCore.Components.ComponentBase> can't follow the preceding guidelines because it doesn't conceptualize what constitutes a valid state for a derived component and it doesn't itself implement <xref:System.IDisposable> or <xref:System.IAsyncDisposable>. If <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> returns an incomplete <xref:System.Threading.Tasks.Task> that doesn't use a <xref:System.Threading.CancellationToken> and the component is disposed before the <xref:System.Threading.Tasks.Task> completes, <xref:Microsoft.AspNetCore.Components.ComponentBase> still calls <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSet%2A> and awaits <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A>. If a disposable component doesn't use a <xref:System.Threading.CancellationToken>, <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSet%2A> and <xref:Microsoft.AspNetCore.Components.ComponentBase.OnParametersSetAsync%2A> should check if the component is disposed.

## Avoid thread-blocking calls

Generally, don't call the following methods in components. The following methods block the execution thread and thus block the app from resuming work until the underlying <xref:System.Threading.Tasks.Task> is complete:
Expand Down