Skip to content

Commit 5601ac1

Browse files
authored
Component lifecycle and disposal concepts (#34800)
1 parent d9e099e commit 5601ac1

File tree

2 files changed

+23
-4
lines changed

2 files changed

+23
-4
lines changed

aspnetcore/blazor/components/lifecycle.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,8 @@ protected override async Task OnInitializedAsync()
197197

198198
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.
199199

200+
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).
201+
200202
Blazor apps that prerender their content on the server call <xref:Microsoft.AspNetCore.Components.ComponentBase.OnInitializedAsync%2A> *twice*:
201203

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

217219
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.
218220

219-
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.
221+
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.
220222

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

@@ -316,7 +318,9 @@ protected override async Task OnParametersSetAsync()
316318

317319
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.
318320

319-
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.
321+
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.
322+
323+
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).
320324

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

@@ -422,7 +426,7 @@ Even if you return a <xref:System.Threading.Tasks.Task> from <xref:Microsoft.Asp
422426
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.
423427
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.
424428

425-
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.
429+
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.
426430

427431
## Base class lifecycle methods
428432

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

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

766-
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.
770+
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.
767771

768772
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.
769773

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

776+
For more information, see the introductory remarks of <xref:blazor/components/sync-context>.
777+
772778
### Disposal of JavaScript interop object references
773779

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

951957
For more information, see:
952958

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

aspnetcore/blazor/components/synchronization-context.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,18 @@ Blazor uses a synchronization context (<xref:System.Threading.SynchronizationCon
1616

1717
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.
1818

19+
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.
20+
21+
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.
22+
23+
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.
24+
25+
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.
26+
27+
<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>.
28+
29+
<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.
30+
1931
## Avoid thread-blocking calls
2032

2133
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:

0 commit comments

Comments
 (0)