-1

I need to implement async task cancel. I know that CancellationTokenSource would help me in achieving that. But I'm unable to find a proper way.

I have a search textbox, whenever a user types in textbox, for each textchanged event I call GetStocks method as shown below,

public async Task GetStocks()
{
    var stockings = new List<Services.Models.Admin.SiteStockingLevelsModel>();
    IsBusy = true;
    cts?.Cancel();
    cts = new CancellationTokenSource();
    await Task.Run(() => { CreateStockingCollection(); });
    ValidateMaterials();
    IsBusy = false;
}

The CreateStockingCollection method is as shown below,

private void CreateStockingCollection()
{
    var stockings = _siteStockingLevelsService.GetSiteInventoryLevels(SiteId);
    CreateStockingLevelCompareCollection(stockings);
    StockingLevels =
        _mapper.Map<TrulyObservableCollection<SiteStockingLevelsModel>>(stockings);            

    ((INotifyPropertyChanged)StockingLevels).PropertyChanged +=
        (x, y) => CompareStockingChanges();
    CompareStockingChanges();
}

My requirement here is,
Example Suppose user wants to type "Abc". When user types "A" the GetStocks method will be called, immediately the user enters "b" the again the get stocks methods will be called, in this case i want to cancel the previous GetStocks task called with letter "A".

VMAtm
  • 27,943
  • 17
  • 79
  • 125
Vinay
  • 259
  • 4
  • 19
  • 1
    FYI: a better fit for this might be "reactive extensions". Creating task based on text changes events can be massively improved by for example only start the search if there are a minimum of 2 characters and if there has been no change in the search text for 200ms. This will reduce the costly calls. An example can be found here: https://stackoverflow.com/questions/22873541/search-on-textchanged-with-reactive-extensions. – Peter Bons Jul 29 '17 at 10:22
  • 2
    @PeterBons has an excellent point, but besides that, you're not using the token anywhere in the code you posted. You need to pass CTS's token to any tasks that should participate in cooperative cancelation. Then the task should check whether the token says the operation should be canceled. For non-CPU-bound operations, this is not really possible unless the token can be passed all the way down (usually by having all calls async with cancellation token support). That way you could cancel a long-running database query for instance. – Alex Paven Jul 29 '17 at 10:41
  • This is a WPF application. – Vinay Jul 29 '17 at 10:49
  • Could you please provide any implemented example of this scenario. – Vinay Jul 29 '17 at 13:35
  • What are you trying to achieve by typing seems not to be user friendly, just think how quickly an user can type, for me it just not make sense to cancel a task when someone types a letter from a whole word and start a new one having the current string. It looks like you want just to change the input string that is send to the task but that should not generate a new task. Usually the cancellation token source is used when a user cancel a long running task by pressing a button, that stops a task that is also updating a progress bar. – Clock Jul 29 '17 at 14:13

2 Answers2

1

One of the best practices of the async programming in .Net is Async all the way. Looks like your method isn't async-based, neither does it accept CancellationToken. You need to add it to your method, in other way only Task.Run will try to cancel your task, which wouldn't work well.

Also, the only creation of the CancellationTokenSource isn't enough - you need to use it's .Token property in your code - this would exactly that token:

await Task.Run(() => { CreateStockingCollection(); }, cts.Token);
VMAtm
  • 27,943
  • 17
  • 79
  • 125
1

Cancellation is cooperative, so you you need to pass it to your own code and have it respond to that token:

public async Task GetStocks()
{
  var stockings = new List<Services.Models.Admin.SiteStockingLevelsModel>();
  IsBusy = true;
  cts?.Cancel();
  cts = new CancellationTokenSource();
  var token = cts.Token;
  await Task.Run(() => { CreateStockingCollection(token); });
  ValidateMaterials();
  IsBusy = false;
}

private void CreateStockingCollection(CancellationToken token)
{
  var stockings = _siteStockingLevelsService.GetSiteInventoryLevels(SiteId, token);
  CreateStockingLevelCompareCollection(stockings);
  StockingLevels =
      _mapper.Map<TrulyObservableCollection<SiteStockingLevelsModel>>(stockings);            

  ((INotifyPropertyChanged)StockingLevels).PropertyChanged +=
      (x, y) => CompareStockingChanges();
  CompareStockingChanges();
}

Here I'm passing it to GetSiteInventoryLevels, which sounds like that would be the long-running part of this work. GetSiteInventoryLevels must now take the CancellationToken and pass it along to whatever APIs it's using.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810