mrpmorris / Fluxor

Fluxor is a zero boilerplate Flux/Redux library for Microsoft .NET and Blazor.
MIT License
1.23k stars 139 forks source link

Guidance on AsyncDisposable in 6.0.0 #500

Open brettwinters opened 1 week ago

brettwinters commented 1 week ago

I'm a bit confused about how to properly work with the new async disposable in 6.0.0 Before (<6.0.0): ```c# @inherits FluxorComponent // UI @code { protected override void Dispose( bool disposing) { // dispose of my stuff like: ActionSubscriber.UnsubscribeFromAllActions(this); base.Dispose(disposing); } } ``` Now It seems I should do this: ```c# @inherits FluxorComponent @Implements IAsyncDisposable // UI @code { public async ValueTask DisposeAsync() { // dispose of my stuff like: await _someThing.DisposeAsync(); ActionSubscriber.UnsubscribeFromAllActions(this); await base.DisposeAsyncCore(true); } } ``` Or should I call `base.DisposeAsync()` or alternatively override `DisposeAsyncCore(true or false?)` - but this doesn't get called when the component is disposed... Do I still need to call `ActionSubscriber.UnsubscribeFromAllActions(this);` if I'm implementing `FluxorComponent`?

stagep commented 1 week ago

The equivalent of

protected override void Dispose(bool disposing)
{
    // dispose of my stuff like:
    ActionSubscriber.UnsubscribeFromAllActions(this);
    base.Dispose(disposing);
}

would be

protected override ValueTask DisposeAsyncCore(bool disposing)
{
     // dispose of my stuff like:
     ActionSubscriber.UnsubscribeFromAllActions(this);
    return base.DisposeAsyncCore(disposing);
}

although the following check of disposing should have always been included

protected override ValueTask DisposeAsyncCore(bool disposing)
{
     // dispose of my stuff like:
    if(disposing)
    {
         ActionSubscriber.UnsubscribeFromAllActions(this);
    }
    return base.DisposeAsyncCore(disposing);
}

To answer your question about what needs to be disposed, I would need to know what you have in OnInitializedAsync(). For example, if you are subscribing to a StateChanged event handler then you need to unsubscribe in DisposeAsyncCore().

brettwinters commented 1 week ago

Hi @stagep

Thanks for getting back to me

Overriding DisposeAsyncCore in my component doesn't seem to get called when the component is disposed

But I just double checked the source code

And saw that it does in fact call await DisposeAsyncCore(true).ConfigureAwait(false);

/// <summary>
    /// Disposes of the component and unsubscribes from any state
    /// </summary>
    public async ValueTask DisposeAsync()
    {
        if (Disposed)
            return;
        Disposed = true;

        StateHasChangedThrottler.Dispose();
        await DisposeAsyncCore(true).ConfigureAwait(false); 
        GC.SuppressFinalize(this);
    }

I wonder if Disposed is always true so my override of DisposeAsyncCore is never called....I'm using Blazor WASM 8.0. if that makes any difference....

stagep commented 1 week ago

Try the following. Add this to your component

protected override Task OnInitializedAsync()
{
    return base.OnInitializedAsync();
}

and put a breakpoint on "return base.OnInitializedAsync()" and put a breakpoint in DisposeAsyncCore().

Do you have either of the following 2 scenarios occurring? Neither breakpoint is hit, or both breakpoints are hit.