diff --git a/src/Component/BlazorComponent/Abstracts/Components/NextTickComponentBase.cs b/src/Component/BlazorComponent/Abstracts/Components/NextTickComponentBase.cs index 591127828..8c23cf4ea 100644 --- a/src/Component/BlazorComponent/Abstracts/Components/NextTickComponentBase.cs +++ b/src/Component/BlazorComponent/Abstracts/Components/NextTickComponentBase.cs @@ -125,30 +125,26 @@ protected async Task Retry(Func callback, Func @while, int retryTime } } - protected virtual async ValueTask DisposeAsync(bool disposing) + protected virtual ValueTask DisposeAsyncCore() => ValueTask.CompletedTask; + + public async ValueTask DisposeAsync() { if (IsDisposed) { - await ValueTask.CompletedTask; + return; } - IsDisposed = true; - } - - public async ValueTask DisposeAsync() - { try { - await DisposeAsync(true); - GC.SuppressFinalize(this); + await DisposeAsyncCore().ConfigureAwait(false); } catch (JSDisconnectedException) { // ignored } - // HACK: remove this after https://github.com/dotnet/aspnetcore/issues/52119 is fixed catch (JSException e) { + // HACK: remove this after https://github.com/dotnet/aspnetcore/issues/52119 is fixed if (e.Message.Contains("has it been disposed") && (OperatingSystem.IsWindows() || OperatingSystem.IsAndroid() || OperatingSystem.IsIOS())) { return; @@ -156,11 +152,8 @@ public async ValueTask DisposeAsync() throw; } - } - ~NextTickComponentBase() - { - // Finalizer calls Dispose(false) - _ = DisposeAsync(false); + GC.SuppressFinalize(this); + IsDisposed = true; } } diff --git a/src/Component/BlazorComponent/Components/App/BApp.razor.cs b/src/Component/BlazorComponent/Components/App/BApp.razor.cs index 5bb4f9fdb..d34a7b176 100644 --- a/src/Component/BlazorComponent/Components/App/BApp.razor.cs +++ b/src/Component/BlazorComponent/Components/App/BApp.razor.cs @@ -24,11 +24,11 @@ private void OnStateChanged(object? sender, EventArgs e) InvokeAsync(StateHasChanged); } - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { PopupProvider.StateChanged -= OnStateChanged; - await base.DisposeAsync(disposing); + await base.DisposeAsyncCore(); } } } diff --git a/src/Component/BlazorComponent/Components/Breadcrumbs/BBreadcrumbsItem.razor.cs b/src/Component/BlazorComponent/Components/Breadcrumbs/BBreadcrumbsItem.razor.cs index beeaef6a3..033509920 100644 --- a/src/Component/BlazorComponent/Components/Breadcrumbs/BBreadcrumbsItem.razor.cs +++ b/src/Component/BlazorComponent/Components/Breadcrumbs/BBreadcrumbsItem.razor.cs @@ -118,11 +118,11 @@ private bool UpdateActiveForRoutable() return matched != Matched; } - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { NavigationManager.LocationChanged -= OnLocationChanged; - await base.DisposeAsync(disposing); + await base.DisposeAsyncCore(); } } } diff --git a/src/Component/BlazorComponent/Components/Form/BForm.razor.cs b/src/Component/BlazorComponent/Components/Form/BForm.razor.cs index 87b668caa..1eba401c2 100644 --- a/src/Component/BlazorComponent/Components/Form/BForm.razor.cs +++ b/src/Component/BlazorComponent/Components/Form/BForm.razor.cs @@ -311,11 +311,11 @@ private async Task UpdateValue(bool val) } } - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { _editContextValidation?.Dispose(); - await base.DisposeAsync(disposing); + await base.DisposeAsyncCore(); } } } diff --git a/src/Component/BlazorComponent/Components/I18n/BI18n.razor.cs b/src/Component/BlazorComponent/Components/I18n/BI18n.razor.cs index cbd0c0eed..46fbb5ba2 100644 --- a/src/Component/BlazorComponent/Components/I18n/BI18n.razor.cs +++ b/src/Component/BlazorComponent/Components/I18n/BI18n.razor.cs @@ -102,9 +102,9 @@ private List GetSegments(string value) private record I18nValueSegment(string Text, int PlaceholderIndex = -1); - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { I18n.CultureChanged -= I18nOnCultureChanged; - await base.DisposeAsync(disposing); + await base.DisposeAsyncCore(); } } diff --git a/src/Component/BlazorComponent/Components/Input/BInput.razor.Validatable.cs b/src/Component/BlazorComponent/Components/Input/BInput.razor.Validatable.cs index 878004de3..a3fd1a066 100644 --- a/src/Component/BlazorComponent/Components/Input/BInput.razor.Validatable.cs +++ b/src/Component/BlazorComponent/Components/Input/BInput.razor.Validatable.cs @@ -526,14 +526,14 @@ protected virtual void HandleOnValidationStateChanged(object? sender, Validation InvokeStateHasChanged(); } - protected override ValueTask DisposeAsync(bool disposing) + protected override ValueTask DisposeAsyncCore() { if (EditContext != null) { EditContext.OnValidationStateChanged -= HandleOnValidationStateChanged; } - return base.DisposeAsync(disposing); + return base.DisposeAsyncCore(); } } } diff --git a/src/Component/BlazorComponent/Components/Input/InputJSModule.cs b/src/Component/BlazorComponent/Components/Input/InputJSModule.cs index 2411d41c0..7ad01f50b 100644 --- a/src/Component/BlazorComponent/Components/Input/InputJSModule.cs +++ b/src/Component/BlazorComponent/Components/Input/InputJSModule.cs @@ -69,7 +69,7 @@ public async Task SetValue(string? val) await _instance.InvokeVoidAsync("setValue", val); } - protected override async ValueTask DisposeAsync() + protected override async ValueTask DisposeAsyncCore() { _isDisposed = true; diff --git a/src/Component/BlazorComponent/Components/ItemGroup/BRoutableGroupItem.razor.cs b/src/Component/BlazorComponent/Components/ItemGroup/BRoutableGroupItem.razor.cs index 9de480a3b..f516fbff3 100644 --- a/src/Component/BlazorComponent/Components/ItemGroup/BRoutableGroupItem.razor.cs +++ b/src/Component/BlazorComponent/Components/ItemGroup/BRoutableGroupItem.razor.cs @@ -90,10 +90,10 @@ private async Task UpdateActiveForRoutable() protected virtual Task OnActiveUpdatedForRoutable() => Task.CompletedTask; - protected override ValueTask DisposeAsync(bool disposing) + protected override ValueTask DisposeAsyncCore() { NavigationManager.LocationChanged -= OnLocationChanged; - return base.DisposeAsync(disposing); + return base.DisposeAsyncCore(); } } diff --git a/src/Component/BlazorComponent/Components/List/BListGroup.razor.cs b/src/Component/BlazorComponent/Components/List/BListGroup.razor.cs index 1da656116..04ab12855 100644 --- a/src/Component/BlazorComponent/Components/List/BListGroup.razor.cs +++ b/src/Component/BlazorComponent/Components/List/BListGroup.razor.cs @@ -171,12 +171,12 @@ private async Task UpdateValue(bool value) } } - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { List?.Unregister(this); NavigationManager.LocationChanged -= OnLocationChanged; - await base.DisposeAsync(disposing); + await base.DisposeAsyncCore(); } } } diff --git a/src/Component/BlazorComponent/Components/Menu/BMenu.razor.cs b/src/Component/BlazorComponent/Components/Menu/BMenu.razor.cs index 2d89d2423..4657ed03f 100644 --- a/src/Component/BlazorComponent/Components/Menu/BMenu.razor.cs +++ b/src/Component/BlazorComponent/Components/Menu/BMenu.razor.cs @@ -249,7 +249,7 @@ private double CalcLeftAuto() return Dimensions.Activator.Left - DefaultOffset * 2; } - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { if (Module is not null) { @@ -263,6 +263,6 @@ protected override async ValueTask DisposeAsync(bool disposing) } } - await base.DisposeAsync(disposing); + await base.DisposeAsyncCore(); } } diff --git a/src/Component/BlazorComponent/Components/NavigationDrawer/BNavigationDrawer.razor.cs b/src/Component/BlazorComponent/Components/NavigationDrawer/BNavigationDrawer.razor.cs index 038e267ca..e19785cc0 100644 --- a/src/Component/BlazorComponent/Components/NavigationDrawer/BNavigationDrawer.razor.cs +++ b/src/Component/BlazorComponent/Components/NavigationDrawer/BNavigationDrawer.razor.cs @@ -230,7 +230,7 @@ public async Task HandleOnOutsideClickAsync() IsActive = false; } - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { await OutsideClickJsModule.UnbindAndDisposeAsync(); } diff --git a/src/Component/BlazorComponent/JSInterop/JSModule.cs b/src/Component/BlazorComponent/JSInterop/JSModule.cs index 2761c5077..b3c7ea528 100644 --- a/src/Component/BlazorComponent/JSInterop/JSModule.cs +++ b/src/Component/BlazorComponent/JSInterop/JSModule.cs @@ -6,6 +6,8 @@ public abstract class JSModule : IAsyncDisposable { private readonly Lazy> _moduleTask; + protected readonly CancellationTokenSource _cts = new(); + private bool _isDisposed; protected JSModule(IJSRuntime js, string moduleUrl) => _moduleTask = new Lazy>(() => js.InvokeAsync("import", moduleUrl).AsTask()); @@ -14,6 +16,11 @@ protected async ValueTask InvokeVoidAsync(string identifier, params object?[]? a { var module = await _moduleTask.Value; + if (_cts.IsCancellationRequested) + { + return; + } + try { await module.InvokeVoidAsync(identifier, args); @@ -24,37 +31,63 @@ protected async ValueTask InvokeVoidAsync(string identifier, params object?[]? a } } - protected async ValueTask InvokeAsync(string identifier, params object?[]? args) + protected async ValueTask InvokeAsync(string identifier, params object?[]? args) { var module = await _moduleTask.Value; + if (_cts.Token.IsCancellationRequested) + { + return default; + } + try { return await module.InvokeAsync(identifier, args); } catch (JSDisconnectedException) { - return default(T); + return default; } } - protected virtual ValueTask DisposeAsync() => ValueTask.CompletedTask; + protected virtual ValueTask DisposeAsyncCore() => ValueTask.CompletedTask; async ValueTask IAsyncDisposable.DisposeAsync() { - if (_moduleTask.IsValueCreated) + if (_isDisposed) + { + return; + } + + _cts.Cancel(); + + if (_moduleTask.IsValueCreated && !_moduleTask.Value.IsFaulted) { var module = await _moduleTask.Value; try { - await DisposeAsync(); - await module.DisposeAsync(); + await DisposeAsyncCore().ConfigureAwait(false); + await module.DisposeAsync().ConfigureAwait(false); } catch (JSDisconnectedException) { // ignored } + catch (JSException e) + { + // HACK: remove this after https://github.com/dotnet/aspnetcore/issues/52119 is fixed + if (e.Message.Contains("has it been disposed") && (OperatingSystem.IsWindows() || OperatingSystem.IsAndroid() || OperatingSystem.IsIOS())) + { + return; + } + + throw; + } } + + _isDisposed = true; + //_cts.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/src/Component/BlazorComponent/Mixins/Activatable/ActivatableJsModule.cs b/src/Component/BlazorComponent/Mixins/Activatable/ActivatableJsModule.cs index 58e3a171b..ab572589c 100644 --- a/src/Component/BlazorComponent/Mixins/Activatable/ActivatableJsModule.cs +++ b/src/Component/BlazorComponent/Mixins/Activatable/ActivatableJsModule.cs @@ -79,7 +79,7 @@ public async Task OnClick(MouseEventArgs args) await _owner.HandleOnClickAsync(args); } - protected override async ValueTask DisposeAsync() + protected override async ValueTask DisposeAsyncCore() { _selfReference?.Dispose(); diff --git a/src/Component/BlazorComponent/Mixins/Groupable/BGroupable.cs b/src/Component/BlazorComponent/Mixins/Groupable/BGroupable.cs index 883ce7dc0..5b3579f86 100644 --- a/src/Component/BlazorComponent/Mixins/Groupable/BGroupable.cs +++ b/src/Component/BlazorComponent/Mixins/Groupable/BGroupable.cs @@ -188,14 +188,14 @@ protected async Task SetInternalIsActive(bool val, bool force = false) } } - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { if (Matched && this is IGroupable item) { ItemGroup!.Unregister(item); } - await base.DisposeAsync(disposing); + await base.DisposeAsyncCore(); } } } diff --git a/src/Component/BlazorComponent/Mixins/Menuable/BMenuable.cs b/src/Component/BlazorComponent/Mixins/Menuable/BMenuable.cs index 562145d78..5047202a3 100644 --- a/src/Component/BlazorComponent/Mixins/Menuable/BMenuable.cs +++ b/src/Component/BlazorComponent/Mixins/Menuable/BMenuable.cs @@ -457,7 +457,7 @@ protected virtual Task DeactivateAsync() return Task.CompletedTask; } - protected override async ValueTask DisposeAsync(bool disposing) + protected override async ValueTask DisposeAsyncCore() { Window.OnResize -= HandleOnResizeAsync; @@ -473,7 +473,7 @@ protected override async ValueTask DisposeAsync(bool disposing) // ignored } - await base.DisposeAsync(disposing); + await base.DisposeAsyncCore(); } } } diff --git a/src/Component/BlazorComponent/Mixins/OutsideClick/OutsideClickJSModule.cs b/src/Component/BlazorComponent/Mixins/OutsideClick/OutsideClickJSModule.cs index 796c94778..76bf32f7f 100644 --- a/src/Component/BlazorComponent/Mixins/OutsideClick/OutsideClickJSModule.cs +++ b/src/Component/BlazorComponent/Mixins/OutsideClick/OutsideClickJSModule.cs @@ -46,7 +46,14 @@ public async Task UpdateDependentElementsAsync(params string[] selectors) /// /// Remove event listener from document and dispose this module /// - public async ValueTask UnbindAndDisposeAsync() => await DisposeAsync(); + + public async ValueTask UnbindAndDisposeAsync() + { + if (this is IAsyncDisposable asyncDisposable) + { + await asyncDisposable.DisposeAsync(); + } + } /// /// Remove event listener from document @@ -61,7 +68,7 @@ public async Task OnOutsideClick() await _owner.HandleOnOutsideClickAsync(); } - protected override async ValueTask DisposeAsync() + protected override async ValueTask DisposeAsyncCore() { await UnbindAsync();