I have custom authentication provider to provide refresh-less login/logout. If I login/logout on page inheriting OwningComponentBase then I get System.ObjectDisposedException
Not so sure about reproducing steps but mine steps are:
1) Going to page with child component having OwningComponentBase
2) Login/Logout
3) Receive System.ObjectDisposedException
blazor.server.js:15 [2020-04-08T17:04:44.371Z] Error: System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'Category'.
at Microsoft.AspNetCore.Components.OwningComponentBase`1.get_Service()
at Shople.Shared.Category.OnParametersSetAsync() in F:\ASP.NET Core\Shople\src\Shared\Category.razor:line 355
at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
Component::OnParametersSetAsync (component implement IDisposable)
protected override async Task OnParametersSetAsync()
{
Products = null;
FilterItems = await Service?.GetCategoryFiltersAsync(Model.Category.Id); // OK, NO EXCEPTION
StateHasChanged();
Products = (await Service?.GetCategoryProductsAsync(Model.Category.Id, DisplayOrder, Page)).DiscountList((await AuthenticationStateTask).User); // EXCEPTION, Service is disposed... LINE 355
}
@glararan thanks for contacting us.
It is likely this is caused by the call to StateHasChanged() after the async work you are doing. You should pass in a cancellation token to your GetCategoryFiltersAsync and to GetCategoryFiltersAsync and trigger cancellation if the component gets disposed.
@javiercn thanks for reply. Could you please provide link to docs how to prevent it? Im not so much familiar with cancellation token.
However wouldnt be simple put Products = await... into condition if Service is not disposed? Shouldnt OnParametersSetAsync be cancelled when OwningComponentBase
@glararan
I think from your stack trace the call to StateHasChanged looks to be causing one of your services to be disposed - presumably because its calling Dispose on the component that inherits OwningConponentBase? Maybe try putting in some logging in your Dispose methods to see when this happens. If your OwningComponentBase is being disposed, it will dispose any services that are resolved from its IServiceScope so on the next line where you use some services I assume atleast one of those services was "owned" and was subsequently just disposed. You'd need to detect when your component and services get disposed so that you don't carry on using them. Its not good to keep hold of references to objects outside of their lifetime scope - if OwningComponentBase is your child component and its being disposed / removed from the render tree, then any services that it owns shouldn't be used outside/ above its render tree hierarchy I.e in parent components etc. Not 100% sure whether that is what is happening here but just saying.
Shouldnt OnParametersSetAsync be cancelled when OwningComponentBase is disposed?
If the disposal is happening on another thread, the thread that is half way through executing OnParametersSetAsync won't magically know about this and abort as far as I am aware.. so you'd have to detect this yourself and guard around it. Cancellation tokens are one way to signal between asynchronous operations that is worth looking into with Async being a pretty fundamental part of the stack now.
So I traced it.
Little bit more code about my component
calling
GetCategoryFiltersAsync runs
using (IServiceScope scope = scopeFactory.CreateScope())
{
DatabaseContext db = scope.ServiceProvider.GetRequiredService<DatabaseContext>();
T item = await DbSet(db).SingleOrDefaultAsync(m => m...., cancellationToken) as T; // FROM THIS POINT Visual Studio jumps into Dispose method of component, NOT service Dispose as the service doesnt implements IDisposable
if (item is null) // After dispose continuing here
throw new NullReferenceException("..");
return .....ToList();
}
Blazor component Dispose
protected override void Dispose(bool disposing)
{
if (disposing)
Disposable?.Dispose();
}
From this point Visual Studio returns to Method above to condition
However after I return into component OnParametersSetAsync
FilterItems = await Service?.GetCategoryFiltersAsync(Model.Category.Id, cancellationToken);
if (cancellationToken.IsCancellationRequested) // On this point SERVICE is already disposed
return;
StateHasChanged();
I am still missing how my GetCategoryFilterAsync jumps into Dispose or rather how to detect Dispose calling to cancel task inside GetCategoryFilterAsync
@glararan here is an example on how to do this.
https://github.com/javiercn/CancellableWorkBlazor
@glararan here is an example on how to do this.
https://github.com/javiercn/CancellableWorkBlazor
Works like charm! Thank you for example.
Most helpful comment
@glararan here is an example on how to do this.
https://github.com/javiercn/CancellableWorkBlazor