Marius Kazlauskas
Marius Kazlauskas

Reputation: 41

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);
    }
}

Upvotes: 4

Views: 2531

Answers (2)

dmitry.bogatko
dmitry.bogatko

Reputation: 1219

I was able to solve the issue just by passing cancellation token to delay task and wrapping everything into try-catch block

private async Task DoWithDelay()
{
    await _cancellationTokenSource.CancelAsync();
    
    _cancellationTokenSource = new CancellationTokenSource();
    _cancellationToken = _cancellationTokenSource.Token;

    try
    {
        var delayTask = Task.Delay(TimeSpan.FromSeconds(2), _cancellationToken);
        await delayTask.ContinueWith(async (_, _) =>
        {
            var result= await DoSomething();

        }, null, TaskContinuationOptions.NotOnCanceled);
    }
    catch (OperationCanceledException)
    {
        //ignored
    }
}

Upvotes: 0

Marius Kazlauskas
Marius Kazlauskas

Reputation: 41

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.

Upvotes: 2

Related Questions