Cancelling an Async Task When TextBox Text Changed Then Restart It

Viewed 200

I've .NET app and there are DataGridView and TextBox on my GUI. What I want to do is when user change TextBox text, update DataGridView where cells contains this text. But this search should run as async task because if it's not, it causes freeze on GUI. Everytime when user changed TextBox text, my app should cancel if another search task running and rerun it again to search according to new search values. Here is my code;

CancellationTokenSource cts = new CancellationTokenSource();
private async void TextBox1_Changed(object sender, EventArgs e)    
{
    cts.Cancel();
    CancellationToken ct = cts.Token;
    try
    {
        await Task.Run(() =>
        {
            System.Diagnostics.Debug.WriteLine("Task started");

            // Searching here.

            System.Diagnostics.Debug.WriteLine("Task finished");
        }, cts.Token);
    }
    catch
    {
        System.Diagnostics.Debug.WriteLine("Cancelled");
    }
}

On my code, tasks are canceled without it's started. I only see "Cancelled" line on debug console. I should cancel tasks because if I don't their numbers and app's CPU usage increases. Is there way to do that ?

2 Answers

Like Rand Random said, i should decleare new CancellationTokenSource object. I have edited my code like this and it's worked. Code should be like that:

CancellationTokenSource cts = new CancellationTokenSource();
private async void TextBox1_Changed(object sender, EventArgs e)    
{
    cts.Cancel();
    cts.Dispose();
    cts = new CancellationTokenSource();
    try
    {
        await Task.Run(() =>
        {
            System.Diagnostics.Debug.WriteLine("Task started");

            // Searching here.

            System.Diagnostics.Debug.WriteLine("Task finished");
        }, cts.Token);
    }
    catch
    {
        System.Diagnostics.Debug.WriteLine("Cancelled");
    }
}

If you want to make life easy for yourself, you should consider using Microsoft's Reactive Framework (aka Rx)

I'm assuming you can write this method:

async Task<string[]> DoSearchAsync(string text, CancellationToken ct)
{
    /* you implement this method */
}

Then you can do this:

    private IDisposable _searchSubscription = null;

    private void Form1_Load(object sender, EventArgs e)
    {
        _searchSubscription =
            Observable
                .FromEventPattern(h => TextBox1.TextChanged += h, h => TextBox1.TextChanged -= h)
                .Throttle(TimeSpan.FromMilliseconds(400.0))
                .Select(ep => TextBox1.Text)
                .Select(text => Observable.FromAsync(ct => DoSearchAsync(text, ct)))
                .Switch()
                .ObserveOn(TextBox1)
                .Subscribe(results => { /* update your UI */ });
    }

Now this watches your TextChanged event, waits 400.0 milliseconds in case you type another character - there's no sense firing off a load of searches if the user is still typing - and then it calls your new DoSearchAsync method.

Then it does a Switch which effectively means cancel any in-flight searchs and start this new one.

Finally it marshalls back to the UI thread and then gives you the results of the search so that you can update the UI.

It just handles all of the calling, background thread calls, marshalling back to the UI, all for you.

If you want to shut it down, just call _searchSubscription.Dispose().

NuGet System.Reactive.Windows.Forms and add using System.Reactive.Linq; to get the bits.

Related