【问题标题】:run Task as I type with linq(cancel previous Task if still running)在我使用 linq 键入时运行任务(如果仍在运行,请取消上一个任务)
【发布时间】:2014-08-13 15:40:38
【问题描述】:

我想创建一个在我输入时使用 linq 搜索的任务,如果用户输入另一个字符,它应该取消任务并重新创建搜索,我有以下代码:

private Task SearchChannels;
private CancellationTokenSource cancelSearch;

public void PopulateChannels(string newValue)
{
    IsSearchingChannels = true; //This just shows a progressbar
    if (SearchChannels != null && cancelSearch!= null)
        if (SearchChannels.Status == TaskStatus.Running || 
            SearchChannels.Status == TaskStatus.WaitingToRun || 
            SearchChannels.Status == TaskStatus.WaitingForActivation || 
            SearchChannels.Status == TaskStatus.WaitingForChildrenToComplete) 
        {
            cancelSearch.Cancel();
            SearchChannels.Wait();
        }
    cancelSearch = new CancellationTokenSource();
    SearchChannels = new Task(() => Channels = new PagedObservableCollection<Channel>(ContractManager.Channels.Where(x => x.Name.ToLower().StartsWith(newValue)).AsParallel().WithCancellation(cancelSearch.Token).ToList()), cancelSearch.Token); //PagedObservableCollection is just a simple class with a list that keeps all items and an ObservableCollection for current items shown

    SearchChannels.Start();
    SearchChannels.ContinueWith((continuation) => IsSearchingChannels = false); // this just hides the progressbar when done
}

我得到这个异常:

'System.OperationCanceledException' 类型的异常 发生在System.Core.dll 但未在用户代码中处理

附加信息:操作已取消。

我是任务和取消令牌的初学者,有人可以从这里指导我走正确的道路吗?我基本上希望任务检查它是否已经运行,取消它,然后使用新值再次运行它(我想让这个“SearchBox”功能类似于 Visual Studio 在解决方案资源管理器中的搜索,它会在你输入时搜索)

【问题讨论】:

  • 你看过Rx.net Buffer().Select(searchString =&gt; Observable.StartAsync(cancelToken =&gt; Search(searchString, cancelToken)).Switch()
  • 谢谢,如果没有其他方法,我会研究 Rx,看起来有点学习曲线 :) 我试过你的例子,但我不明白它应该如何以及在哪里使用过,还有 Buffer()。没有给我select之类的方法,我应该使用什么(我已经添加了using System.Reactive.Linq

标签: c# linq entity-framework task cancellation-token


【解决方案1】:

首先,您需要创建一个IObservable&lt;string&gt; 来抽象更改控件上的值。 “最简单”的方法是使用Subject&lt;string&gt;,但很可能是错误的方法。

以下是您应该放入 ViewModel 的代码。

IDisposable _searchSubscriber =
    _searchString
         .Buffer(TimeSpan.FromMillisecond(300))
         .Select(searchString => 
                Observable.StartAsync(cancelToken => 
                      Search(searchString, cancelToken)
                ).Switch()
         .ObserveOn(new DispatcherScheduler())
         .Subscribe(results => Channels = results);

public Task<List<Channel>> Search(string searchTerm, CancellationToken cancel)
{
    var query = dbContext.Channels.Where(x => x.Name.StartsWith(searchTerm));
    return query.ToListAsync(cancel);
}

private BehaviorSubject<string> _searchString = new BehaviorSubject<string>("");
public string SearchString
{
    get { return _searchString.Value; }
    set { _searchString.OnNext(value); OnPropertyChanged("SearchString"); }
}

Rx.net 是一个非常强大的库,这当然意味着它确实有一些学习曲线(尽管事实上这很复杂,因为您的问题很复杂)。

让我把它列出来......

.Buffer(TimeSpan.FromMilliseconds(300)) 对您的查询进行去抖动处理,因此它仅每 300 毫秒运行一次查询。

Observable.StartAsync(cancelToken =&gt; Search(searchString, cancelToken)) 为搜索任务创建了一个 Observable,当它被释放时会被取消。

Select(x =&gt; ...).Switch() 只获取最新的查询结果,并处理最后一个查询。

Subscribe(results =&gt; ...) 处理结果。

【讨论】:

  • 我查询数据库对象,例如,获取Channel表中Name字段以'ace'开头的所有记录。我正在使用WPFEntityFrameWork,所以基本上我的“SearchBox”绑定到视图模型中的ChannelSearchString 属性,设置器调用需要异步查询数据库的PopulateChannels(string newValue) 方法。显示搜索结果的列表框绑定到 Channels 属性,每次调用 PopulateChannels 时都会更改。
  • 我无法直接访问 dbContext,它被封装在存储库 (ContractManager) 中,我的问题是 ToListAsync,存储库将列表作为 IEnumerable(ObservableCollection) 而不是 IQueryable 发送。除了更改存储库代码之外,您还知道另一种方法吗?感谢您的所有帮助
  • @jclabuschagne 我们需要知道异步和取消的回购代码。这就是您想要支持的。
  • @jclabuschagne 您确实意识到,现在每次调用 ContractManager 时,您都会从数据库中提取每个 Channel 对象,对吗?你知道这真的很糟糕吗?
猜你喜欢
  • 2022-07-11
  • 1970-01-01
  • 2021-10-07
  • 2014-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-08-05
  • 1970-01-01
相关资源
最近更新 更多