2

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

我的第一个想法是 Rx 原始 Throttle 是一个完美的匹配,但它让我达到了一半。

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

问题是,如果用户再次键入限制时间跨度,我想丢弃/跳过/丢弃结果。

例如:

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

现在有了 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)
                         )

             );

}
4

2 回答 2

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。

于 2016-08-25T16:39:30.317 回答
0

如果我正确理解了您想要的内容,则可以以非常直接的方式完成此操作,并且如果您稍微重构代码,则可以进行清理。

首先,将名字和姓氏触发器变成可观察对象。在下面的代码中,我使用了主题,但如果您能够使用静态 Observable 方法将它们“转换”为可观察对象,那就更好了;例如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 有效地对该代码进行单元测试。

希望能帮助到你。

于 2016-08-25T16:38:42.537 回答