C# – HTTP Async/Await Task: avoid flooding server with requests

asyncasynchronous-programmingc

I have a scenario where I have a Windows Store Application, there is a page with a search functionality, the user types names in a textbox and the app searches for names similar to the typed text.

Searching is done through calling a RESTful GET Api method. the call takes about 4 seconds to retrieve the results. This uses the c# async/await technique.

What I want to do is to do is to call the HTTP method as the user types, but I want to do this by cancelling any executing tasks and making sure that only one task executes at a time to avoid flooding the server with requests.

I implemented the following approach, it works fine, but I believe there can be better solution:

  • I hold a reference to the async task and a cancellation token source.
  • As the user types, I check if there is a running task, I cancel it and start a new one with the most recent search text.

    private CancellationTokenSource cts = null;
    private Task<List<string>> searchTask;
    
    private async void textbox_TextChanged(object sender, TextChangedEventArgs e)
    {
      //there is already running task
      if (searchTask != null && !searchTask.IsCompleted)
        {
          //cancel the running and start a new one
             cts.Cancel();
             cts = new CancellationTokenSource();
           searchTask=SearchWeb(txtbox.Text, cts.Token);
           searchTask.ContinueWith(t =>{
               //show results
           });
      }
      else
      {
          //start a new one
            searchTask = dataRepository.SearchUsers(txtbox.Text, cts.Token);
            searchTask.ContinueWith(t =>{
              //show results
          });
      }
    }
    

My primary concern is that I don't like the overhead generated with tracking the running tasks. It seems like there should be a more efficient way of doing this that doesn't require cancelling so many requests.

Best Answer

If you wish to reduce the number of starts and cancels - I would put a delay into your execution. Another approach would be to use polling but that's more overhead and more complex; and violates the good policy of "Tell, don't ask".

I think something like this may do the job of only grabbing the search string after a delay, so they can type a few characters before it grabs it and begins the execution. If execution is already occurring you still want to cancel and kick off another delay though. This way if they start and finish typing before the delay is over, it won't be cancelled and it'll execute the query right then.

private CancellationTokenSource cts = null;
private Task<List<string>> searchTask;
private Task delayTask;
private string searchString;

private async void textbox_TextChanged(object sender, TextChangedEventArgs e)
{
  searchString = txtbox.Text;

  if (delayTask != null && !delayTask.IsCompleted)
    return;

  //there is already running task
  if (searchTask != null && !searchTask.IsCompleted)
  {
    //cancel the running and start a new one
    cts.Cancel();
    cts = new CancellationTokenSource();
  }

  ExecuteQueryAfterDelay();
}

private void ExecuteQueryAfterDelay()
{
  delayTask = Task.Delay(2000).ContinueWith(t =>
  {
    searchTask=SearchWeb(searchString, cts.Token);
    searchTask.ContinueWith(t =>
    {
      //show results
    });
  });
}