Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async task cancellation c# xamarin

I have a functionality of search users. I have provided a textview and on that textview changed method I'm firing a method to get data from web server. But I'm facing problem when user types letter, because all the api hits done in async task. Service should be hit after 100 milli-sec of wait, means if user types a letter "a" then doesn't type for 100 milli-sec then We have to hit the service. But if user types "a" then "b" then "c", so one service should be hit for "abc", not for all.

I followed the official link, but it doesn't help me https://msdn.microsoft.com/en-us/library/jj155759.aspx

So basically here is my code

textview.TextChange+= (sender,e) =>{
  CancellationTokenSource cts = new CancellationTokenSource();
      await Task.Delay(500); 
      // here some where I have to pass cancel token
      var lst = await APIClient.Instance.GetUserSearch("/user/get?searchTerm=" + newText, "application/json",cts);
      if (lst != null && lst.Count > 0){ 
        lstSearch.AddRange(lst);
      }
  }

Here is my method to GetUser

 public async Task<JResponse> GetUserSearch<JResponse>(string uri, string contentType,CancellationToken cts)
    {
        try
        {
            Console.Error.WriteLine("{0}", RestServiceBaseAddress + uri);

            string url = string.Format("{0}{1}", RestServiceBaseAddress, uri); 

            var request = (HttpWebRequest)WebRequest.Create(url);
            request.ContentType = contentType;
            if (Utility.CurrentUser != null && !string.IsNullOrWhiteSpace(Utility.CurrentUser.AuthToken))
            {
                request.Headers.Add("api_key", Utility.CurrentUser.AuthToken);
            }
            request.Method = "POST";

            var payload = body.ToString();

            request.ContentLength = payload.Length;

            byte[] byteArray = Encoding.UTF8.GetBytes(body.ToString());
            request.ContentLength = byteArray.Length;

            using (var stream = await request.GetRequestStreamAsync())
            {
                stream.Write(byteArray, 0, byteArray.Length);
                stream.Close();
            }
            using (var webResponse = await request.GetResponseAsync())
            {
                var response = (HttpWebResponse)webResponse;
                using (var reader1 = new StreamReader(response.GetResponseStream()))
                {
                    Console.WriteLine("Finished : {0}", uri);
                    var responseStr = reader1.ReadToEnd();  
                    var responseObj = JsonConvert.DeserializeObject<JResponse>(
                        responseStr,
                        new JsonSerializerSettings()
                        {
                            MissingMemberHandling = MissingMemberHandling.Ignore,
                            NullValueHandling = NullValueHandling.Ignore
                        });
                    return responseObj;
                }
            }
        }
        catch (System.Exception ex)
        {
            Utility.ExceptionHandler("APIClient", "ProcessRequestAsync", ex);
        }

        return default(JResponse);
    }
like image 348
Sagar Panwala Avatar asked Jan 30 '16 07:01

Sagar Panwala


1 Answers

In your example, you are creating a CancellationTokenSource - you need to hold a reference to it, so that the next time the handler is invoked, the previous search can be cancelled. Here is an example console app that you should be able to run, but the important piece is in the handler.

private CancellationTokenSource _cts;

private async void TextChangedHandler(string text)   // async void only for event handlers
{
    try
    {
        _cts?.Cancel();     // cancel previous search
    }
    catch (ObjectDisposedException)     // in case previous search completed
    {
    }

    using (_cts = new CancellationTokenSource())
    {
        try
        {
            await Task.Delay(TimeSpan.FromSeconds(1), _cts.Token);  // buffer

            var users = await _userService.SearchUsersAsync(text, _cts.Token);
            Console.WriteLine($"Got users with IDs: {string.Join(", ", users)}");
        }
        catch (TaskCanceledException)       // if the operation is cancelled, do nothing
        {
        }
    }
}

Be sure to pass the CancellationToken into all of the async methods, including those that perform the web request, this way you signal the cancellation right down to the lowest level.

like image 124
Matt Cole Avatar answered Jan 01 '23 16:01

Matt Cole