Is there an existing issue for this?
Describe the bug
When using BitNumberField (and similarly BitTimePicker, BitDatePicker, BitDateRangePicker) with an OnIncrement/OnDecrement/OnSelectTime callback that causes the component to be disposed (e.g., navigating away or conditionally rendering it out), clicking the increment or decrement button throws an ObjectDisposedException.
There are two related issues:
-
Primary: HandleOnPointerDown awaits ChangeValueAndInvokeEvents (which invokes user callbacks like OnIncrement/OnDecrement or the value change event). If the callback disposes the component, DisposeAsync runs and calls _continuousChangeValueCts.Dispose(). When HandleOnPointerDown resumes, it calls ResetCts() => _cts.Cancel() on an already-disposed CancellationTokenSource => ObjectDisposedException.
-
Secondary (all 5 components): DisposeAsync disposed the CTS without first cancelling it. Since IsCancellationRequested returns false on a disposed-but-uncancelled CTS, the ContinuousChangeValue/Time recursive loop continued running after component disposal, calling StateHasChanged() on a component that should be disposed indefinitely.
Expected Behavior
No exception should be thrown. After a value-change callback disposes the component, all continuations in the pointer-down handler should exit cleanly.
Steps To Reproduce
@page "/bug"
@if (_double == 1)
{
<BitNumberField @bind-Value="_double" Mode="BitSpinButtonMode.Compact" />
}
@if (_time == TimeSpan.Zero)
{
<BitTimePicker @bind-Value="_time" />
}
@* open the picker, then use the time spinners *@
@if (_date?.TimeOfDay == TimeSpan.Zero)
{
<BitDatePicker @bind-Value="_date" ShowTimePicker="true" />
}
@* open the picker, then use the time spinners *@
@if (_dateRange?.StartDate?.TimeOfDay == TimeSpan.Zero)
{
<BitDateRangePicker @bind-Value="_dateRange" ShowTimePicker="true" />
}
@if (_calDate?.TimeOfDay == TimeSpan.Zero)
{
<BitCalendar @bind-Value="_calDate" ShowTimePicker="true" />
}
@code {
double _double = 1;
TimeSpan? _time = TimeSpan.Zero;
DateTimeOffset? _date = DateTimeOffset.UtcNow.Date;
BitDateRangePickerValue? _dateRange = new() { StartDate = DateTimeOffset.UtcNow.Date, EndDate = DateTimeOffset.UtcNow.Date.AddDays(3) };
DateTimeOffset? _calDate = DateTimeOffset.UtcNow.Date;
}
Clicking up/down buttons for all components will trigger the bugs. For the datetime components, no exception is thrown, but they apear periodically because their value changed callbacks are still firing.
Exceptions (if any)
System.ObjectDisposedException: The CancellationTokenSource has been disposed.
at System.Threading.CancellationTokenSource.Cancel()
at Bit.BlazorUI.BitNumberField`1.ResetCts() in C:\Users\Jakub\source\repos\bitplatform-upstream\src\BlazorUI\Bit.BlazorUI\Components\Inputs\NumberField\BitNumberField.razor.cs:line 660
at Bit.BlazorUI.BitNumberField`1.HandleOnPointerDown(Boolean isIncrement) in C:\Users\Jakub\source\repos\bitplatform-upstream\src\BlazorUI\Bit.BlazorUI\Components\Inputs\NumberField\BitNumberField.razor.cs:line 540
at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle, ComponentState owningComponentState)
for spin buttons on BitNumberField, other components will not throw because of synchronous value updates.
.NET Version
10.0.201
Anything else?
No response
Is there an existing issue for this?
Describe the bug
When using
BitNumberField(and similarlyBitTimePicker,BitDatePicker,BitDateRangePicker) with anOnIncrement/OnDecrement/OnSelectTimecallback that causes the component to be disposed (e.g., navigating away or conditionally rendering it out), clicking the increment or decrement button throws anObjectDisposedException.There are two related issues:
Primary:
HandleOnPointerDownawaitsChangeValueAndInvokeEvents(which invokes user callbacks likeOnIncrement/OnDecrementor the value change event). If the callback disposes the component,DisposeAsyncruns and calls_continuousChangeValueCts.Dispose(). WhenHandleOnPointerDownresumes, it callsResetCts()=>_cts.Cancel()on an already-disposedCancellationTokenSource=>ObjectDisposedException.Secondary (all 5 components):
DisposeAsyncdisposed the CTS without first cancelling it. SinceIsCancellationRequestedreturnsfalseon a disposed-but-uncancelled CTS, theContinuousChangeValue/Timerecursive loop continued running after component disposal, callingStateHasChanged()on a component that should be disposed indefinitely.Expected Behavior
No exception should be thrown. After a value-change callback disposes the component, all continuations in the pointer-down handler should exit cleanly.
Steps To Reproduce
Clicking up/down buttons for all components will trigger the bugs. For the datetime components, no exception is thrown, but they apear periodically because their value changed callbacks are still firing.
Exceptions (if any)
for spin buttons on
BitNumberField, other components will not throw because of synchronous value updates..NET Version
10.0.201
Anything else?
No response