Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to cancel previous Task if new request recieved?

I will try to simplify my situation here to be more clean and concise. So, I am working on a WinRT application where user enters text in a TextBox and in its TextChanged event after 2 seconds have elapsed I need to make a remote request to fetch data based on user text.

Now user enters text and a web request has been initialized but immediately user writes another term. So, I need to cancel the first web request and fire the new one.

Consider the following as my code :

private CancellationTokenSource cts;

public HomePageViewModel()
{
    cts = new CancellationTokenSource();
}

private async void SearchPeopleTextChangedHandler(SearchPeopleTextChangedEventArgs e)
{
    //Cancel previous request before making new one        

    //GetMembers() is using simple HttpClient to PostAsync() and get response
    var members = await _myService.GetMembers(someId, cts.Token);

    //other stuff based on members
}

I know CancellationToken plays a role here but I just cannot figure out how.

like image 352
lbrahim Avatar asked Dec 15 '22 13:12

lbrahim


2 Answers

You've already almost got it. The core idea is that a single CancellationTokenSource can only be canceled once, so a new one has to be created for each operation.

private CancellationTokenSource cts;

private async void SearchPeopleTextChangedHandler(SearchPeopleTextChangedEventArgs e)
{
  // If there's a previous request, cancel it.
  if (cts != null)
    cts.Cancel();

  // Create a CTS for this request.
  cts = new CancellationTokenSource();

  try
  {
    var members = await _myService.GetMembers(someId, cts.Token);

    //other stuff based on members
  }
  catch (OperationCanceledException)
  {
    // This happens if this operation was cancelled.
  }
}
like image 58
Stephen Cleary Avatar answered Dec 23 '22 07:12

Stephen Cleary


I would implement the GetMembers method like this:

private async Task<List<Member>> GetMembers(int id, CancellationToken token)
    {
        try
        {
            token.ThrowIfCancellationRequested();
            HttpResponseMessage response = null;

            using (HttpClient client = new HttpClient())
            {
                response = await client.PostAsync(new Uri("http://apiendpoint"), content)
                                       .AsTask(token);
            }

            token.ThrowIfCancellationRequested();
            // Parse response and return result
        }
        catch (OperationCanceledException ocex)
        {
            return null;
        }
    }

The rest is just calling the cts.Cancel() method and creating a new instance of CancellationTokenSource before calling GetMembers each time in the handler. Of course, as @Russell Hickey mentioned, cts should be global. (and even static if there are multiple instances of this class and you always want to cancel the GetMembers method when this handler is invoked. Usually I also have a class which wraps the result and has an additional property IsSuccessful to distinguish a real null result from a failed operation.

like image 28
Panayot Todorov Avatar answered Dec 23 '22 06:12

Panayot Todorov