【问题标题】:Task cancelling for an autocomplete field doesn't cancel all previous tasks取消自动完成字段的任务不会取消所有以前的任务
【发布时间】:2015-08-05 11:04:38
【问题描述】:

我有一个搜索方法可以将搜索建议返回到 UI。每次用户在搜索框中输入新字符时都会触发此方法。

我添加了一些取消代码来取消之前的搜索请求。这有效,但并非一直有效。

private CancellationTokenSource cancellationTokenSource;

private async Task UserSearch(string searchCriteria)
{
    Debug.WriteLine("Searching for {0}....", searchCriteria);

    try
    {
        var cts = new CancellationTokenSource();
        this.Suggestions = await this.SearchAsync(searchCriteria, cts.Token);
    }
    catch (OperationCanceledException)
    {
        Debug.WriteLine("Search({0}) cancelled", searchCriteria);
    }
}

private async Task<IList<string>> SearchAsync(string searchCriteria, CancellationToken cancelToken)
{
    CancellationTokenSource previousCts = this.cancellationTokenSource;
    CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancelToken);

    this.cancellationTokenSource = linkedCts;

    // if previous task running cancel it
    if (previousCts != null)
    {
        previousCts.Cancel();
    }

    linkedCts.Token.ThrowIfCancellationRequested();

    List<string> results =
        (await this.searchProvider.SearchAsync(searchCriteria, linkedCts.Token)).ToList();

    Debug.WriteLine("Search({0}) returned {1} records", searchCriteria, results.Count);

    linkedCts.Dispose();
    this.cancellationTokenSource = null;

    return results;
}

例如。我收到以下调试消息:

SearchTerm changing to: Di 
Searching for Di....
SearchTerm changing to: Dia
Searching for Dia....
Search(Di) cancelled
SearchTerm changing to: Diap
Searching for Diap....
Search(Diap) returned 323 records
Search(Dia) returned 3230 records

如您所见,第一次搜索被取消,但第二次没有,并且在最后一次搜索后返回,给用户提供了不正确的结果。

如何确保以前的任务总是被取消?

【问题讨论】:

  • SearchAsync 有什么作用?它真的会响应取消吗?
  • @usr 它本质上是传递给 SQLite.Net QueryAsync 方法 github.com/oysteinkrog/SQLite.Net-PCL/blob/master/src/…
  • 如果您在取消之前已经执行了查询,并且调用的最低 API 在查询时没有监听该令牌,那么您将不会被取消。
  • 感谢@YuvalItzchakov 在this.SearchProvider.SearchAsync() 方法的底部添加一个额外的token.ThowIfCancellationRequested() 似乎可以解决问题。是否有关于多久添加一次此检查的规则?

标签: c# .net asynchronous async-await task-parallel-library


【解决方案1】:

我认为您的解决方案可能有点复杂。您需要做的就是查看是否存在尚未取消的现有操作并取消它。然后执行新的搜索。未经测试,但我认为应该这样做。

private CancellationTokenSource cancellationTokenSource;

private async Task UserSearch(string searchCriteria)
{
    Debug.WriteLine("Searching for {0}....", searchCriteria);

    try
    {       
        if(cancellationTokenSource != null && 
           !cancellationTokenSource.IsCancellationRequested)
        {
            cancellationTokenSource.Cancel();
        }

        cancellationTokenSource = new CancellationTokenSource();

        this.Suggestions = await this.searchProvider.SearchAsync(searchCriteria, linkedCts.Token);

        Debug.WriteLine("Search({0}) returned {1} records", searchCriteria, results.Count);

    }
    catch (OperationCanceledException)
    {
        Debug.WriteLine("Search({0}) cancelled", searchCriteria);
    }
}

【讨论】:

  • 修复方法是在 this.SearchProvider.SearchAsync() 方法中添加一个额外的 token.ThowIfCancellationRequested()。但是我已经根据你的简化版本整理了我的代码。
【解决方案2】:

您取消之前的查询为时已晚,请尽快查询(在收到用户输入后直接查询,而不是在下一个查询中)。

【讨论】:

  • 我已经尝试了您的建议,但仍然得到与问题相同的结果
【解决方案3】:

我想问题是多线程访问this.cancellationTokenSource。如果说线程 2 取消线程 1 太晚(searchProvider.SearchAsync 已完成)this.cancellationTokenSource 可以在分配后清除(即线程 2 执行 this.cancellationTokenSource = linkedCts; 并且线程 1 之后调用 this.cancellationTokenSource = null;)。这将有效地完全禁用线程 2 的取消

因此,您最好在下一个搜索开始之前取消一个搜索,就像@Ned Stoyanov 的建议一样

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多