节流,但如果来得太晚就会丢弃结果

时间:2016-08-25 15:51:42

标签: c# .net system.reactive reactiveui rx.net

我正在编写一个用户界面,用户可以在其中输入搜索字词,列表会不断更新,提供建议。

我的第一个原因是Rx原始Throttle是一个完美的匹配,但它让我在那里一半。

建议需要一段时间才能获取,所以我不是在UI线程上异步获取它们。

问题是如果用户再次输入节流时间范围,我想丢弃/跳过/丢弃结果。

例如:

  • 时间开始,用户按下一个键:0ms
  • 油门设置为100毫秒。
  • 获取需要200毫秒。
  • 在150ms时,用户按下另一个键

现在有了Throttle,第一次获取仍将继续填充gui建议列表。 我想学习的是如何取消首次获取,因为它不再相关? 只有第二个按键才能触发对gui的更新。

这是我试过的

(我使用ReactiveUI,但Q是关于Rx)

public IEnumerable<usp_getOpdrachtgevers_Result> Results { get; set; } // [Reactive] pu

public SearchOpdrachtgeverVM()
{

    this.WhenAnyValue(x => x.FirstName,
                      x => x.LastName
        )
        .Throttle(TimeSpan.FromMilliseconds(200))
        .Subscribe(async vm => Results = await PopulateGrid());
}

private async Task<IEnumerable<usp_getOpdrachtgevers_Result>> PopulateGrid()
{

    return await Task.Run(
             () => _opdrachtgeversCache
                         .Where(x =>
                                x.vNaam.Contains(FirstName)
                                && x.vLastName.Contains(LastName)
                         )

             );

}

2 个答案:

答案 0 :(得分:3)

如果您将异步任务变为Observable,这看起来像是Switch的经典用法:

this.WhenAnyValue(x => x.FirstName,
                  x => x.LastName
    )
    .Throttle(TimeSpan.FromMilliseconds(100)) 
    .Select(l => PopulateGrid().ToObservable())
    .Switch()
    .Subscribe(vm => Results = vm);
在用户输入时,应使用

Throttle来禁止呼叫。所以根据需要调整TimeSpan。

答案 1 :(得分:0)

如果我理解你想要什么,可以用一种非常直接的方式来完成,如果你稍微重构一下代码就可以清理。

首先,将名字和姓氏触发到observables。在下面的代码中,我使用了主题,但如果您能够使用静态Observable方法来转换&#39;则会更好。他们成为可观察者;例如Observable.FromEvent

然后转动代码将结果提取到一个observable中。在下面的代码中,我使用了Observable.Create来返回IEnumerable<string>的流。

最后,您可以使用Switch运算符订阅每个新的GetResults调用,并取消之前对GetResults的调用。

听起来很复杂,但代码很简单:

private Subject<string> _firstName = new Subject<string>();
private Subject<string> _lastName = new Subject<string>();

private Task<IEnumerable<string>> FetchResults(string firstName, string lastName, CancellationToken cancellationToken)
{
    // Fetch the results, respecting the cancellation token at the earliest opportunity
    return Task.FromResult(Enumerable.Empty<string>());
}

private IObservable<IEnumerable<string>> GetResults(string firstName, string lastName)
{
    return Observable.Create<IEnumerable<string>>(
        async observer =>
        {
            // Use a cancellation disposable to provide a cancellation token the the asynchronous method
            // When the subscription to this observable is disposed, the cancellation token will be cancelled.
            CancellationDisposable disposable = new CancellationDisposable();

            IEnumerable<string> results = await FetchResults(firstName, lastName, disposable.Token);

            if (!disposable.IsDisposed)
            {
                observer.OnNext(results);
                observer.OnCompleted();
            }

            return disposable;
        }
    );
}

private void UpdateGrid(IEnumerable<string> results)
{
    // Do whatever
}

private IDisposable ShouldUpdateGridWhenFirstOrLastNameChanges()
{
    return Observable
        // Whenever the first or last name changes, create a tuple of the first and last name
        .CombineLatest(_firstName, _lastName, (firstName, lastName) => new { FirstName = firstName, LastName = lastName })
        // Throttle these tuples so we only get a value after it has settled for 100ms
        .Throttle(TimeSpan.FromMilliseconds(100))
        // Select the results as an observable
        .Select(tuple => GetResults(tuple.FirstName, tuple.LastName))
        // Subscribe to the new results and cancel any previous subscription
        .Switch()
        // Use the new results to update the grid
        .Subscribe(UpdateGrid);
}

快速提示:您应该将明确的调度程序传递到Throttle中,以便您可以使用TestScheduler有效地对此代码进行单元测试。

希望它有所帮助。