1

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.

  • Do you really need the async/await? – Robert Harvey Dec 8 '15 at 15:18
  • @RobertHarvey Yes, I use the System.Net.HttpClient GetAsync method, which is awaitable. do you think there is an alternative? – Mina Samy Dec 8 '15 at 15:21
  • 3
    Can you wait until about 1/2 second of non-typing occurs? That would prevent flooding. It's the same principle that prevents ordinary typing from flooding the screen with auto-repeat characters. As long as the user is still typing, there's not much point in sending AJAX requests, especially if the response takes 4 seconds to return. – Robert Harvey Dec 8 '15 at 15:23
  • @RobertHarvey - HttpClient requires asynchronous calls. WebClient supports both async and synchronous, but WebClient requires a bit more fiddling in order to make it play nicely. – GlenH7 Dec 8 '15 at 15:30
  • @RobertHarvey Sure waiting for 0.5 seconds can be a useful approach, but is there a different way to handle this scenario without adding restrictions from the UI side? – Mina Samy Dec 8 '15 at 15:36
3

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.