Search code examples
blazorblazor-server-sidecancellationtokensourcedebouncing

Debounce implementation with CancellationTokenSource in Blazor server side


I implemented a debounce on input with CancellationTokenSource in Blazor server side app (.net core 3.0).

It works well with input delay as expected, but always writes errors in Debug Output, when typing:

Exception thrown: 'System.InvalidOperationException' in Microsoft.AspNetCore.Components.dll

and when fast typing:

Exception thrown: 'System.Threading.Tasks.TaskCanceledException' in System.Private.CoreLib.dll

Do you have any ideas how to fix it?

You can find the implementation here: https://blazorfiddle.com/s/ij9l55ne

Main page:

@page "/"
@using System.Threading
@using System.Threading.Tasks

<MyChildComponent OnTyping="async e => await OnTypingAsync(e)"/>
<div>@result</div>

@code {
    string result;

    public async Task OnTypingAsync(string myText)
    {
        await Task.Delay(1);//call GetDataAsync(myText) method

        result = myText;
        await InvokeAsync(StateHasChanged);
    }
}

Child component:

@using System.Threading
@using System.Threading.Tasks

<input type="text" @oninput="async e => await OnInput(e)" />

@code {
    [Parameter] public EventCallback<string> OnTyping { get; set; }

    CancellationTokenSource Cts = new CancellationTokenSource();
    CancellationToken Ct;

    public async Task OnInput(ChangeEventArgs e)
    {
        Cts.Cancel();
        Cts = new CancellationTokenSource();
        Ct = Cts.Token;

        await Task.Delay(500, Ct).ContinueWith(async task =>
        {
            if (!task.IsCanceled) {
                await OnTyping.InvokeAsync(e.Value.ToString());
            }
        }, Ct);
    }
}

Solution

  • javiercn provides good idea how to solve this problem on github issue registered by me: https://github.com/aspnet/AspNetCore/issues/16524#issuecomment-546847682

    • You need to always catch OperationCanceledException when using cancellation tokens.
    • You should avoid using continue with in your method as you are not capturing the SynchronizationContext and that's going to produce errors when you try to update the UI from the continuation callback.