【问题标题】:Canceling the previous Async Task using CancellationTokenSource使用 CancellationTokenSource 取消之前的异步任务
【发布时间】:2017-07-29 09:49:16
【问题描述】:

我需要实现async 任务取消。我知道CancellationTokenSource 会帮助我实现这一目标。但是我找不到合适的方法。

我有一个搜索文本框,每当用户在文本框中键入时,对于每个 textchanged 事件,我都会调用GetStocks 方法,如下所示,

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

CreateStockingCollection方法如下图,

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

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

我的要求是,
示例假设用户想要输入“Abc”。当用户键入“A”时,GetStocks 方法将被调用,用户立即输入“b”,将再次调用 get stock 方法,在这种情况下,我想取消之前用字母“A”调用的 GetStocks 任务”。

【问题讨论】:

  • 仅供参考:更适合这种情况的可能是“反应式扩展”。基于文本更改事件创建任务可以通过例如仅在至少有 2 个字符并且搜索文本在 200 毫秒内没有更改时才开始搜索而得到显着改进。这将减少昂贵的通话。可以在此处找到示例:stackoverflow.com/questions/22873541/….
  • @PeterBons 有一个很好的观点,但除此之外,您没有在发布的代码中的任何地方使用令牌。您需要将 CTS 的令牌传递给任何应该参与合作取消的任务。然后任务应该检查令牌是否表示应该取消操作。对于非 CPU 绑定操作,这实际上是不可能的,除非令牌可以一直向下传递(通常通过使所有调用异步并支持取消令牌)。例如,这样您就可以取消长时间运行的数据库查询。
  • 这是一个 WPF 应用程序。
  • 您能否提供此场景的任何实现示例。
  • 你想通过打字来实现什么似乎对用户不友好,想想用户打字的速度有多快,对我来说,当有人输入来自整个单词并开始一个具有当前字符串的新单词。看起来您只想更改发送到任务但不应生成新任务的输入字符串。通常,当用户通过按下按钮取消长时间运行的任务时使用取消令牌源,这会停止也在更新进度条的任务。

标签: c# multithreading asynchronous task task-parallel-library


【解决方案1】:

取消是合作的,因此您需要将其传递给您自己的代码并让它响应该令牌:

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

在这里,我将其传递给GetSiteInventoryLevels,这听起来像是这项工作的长期部分。 GetSiteInventoryLevels 现在必须接受 CancellationToken 并将其传递给它正在使用的任何 API。

【讨论】:

    【解决方案2】:

    .Net 中异步编程的最佳实践之一是Async all the way。看起来您的方法不是基于异步的,它也不接受CancellationToken。您需要将其添加到您的方法中,否则只有Task.Run 会尝试取消您的任务,这不会很好。

    此外,CancellationTokenSource 的唯一创建是不够的 - 您需要在代码中使用它的 .Token 属性 - 这正是该令牌:

    await Task.Run(() => { CreateStockingCollection(); }, cts.Token);
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2017-10-30
      • 2021-05-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多