2016-08-25 49 views
2

我正在編寫一個用戶可以在其中輸入搜索詞的用戶界面,並且列表可以獲得不斷更新的提供建議。節流,但如果它們來得太晚,就會丟棄結果

我的第一個雖然是Rx原始節流閥是一個完美的比賽,但它讓我一半。

建議需要一段時間來獲取,所以我不會在UI線程上異步獲取它們。

問題是,如果用戶再次輸入油門時間間隔,我想放棄/跳過/丟棄結果。

例如:

  • 時間開始,並且用戶按下一個鍵:0毫秒
  • 節氣門被設置爲100毫秒。
  • 獲取需要200ms。
  • 在150ms的用戶與油門按下另一個鍵

現在的第一次提取仍將繼續的填充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) 
         ) 

      ); 

} 

回答

0

如果我知道你想正確的東西,這可以在一個相當簡單的方式進行,如果你稍微重構你的代碼,乾淨。

首先,將名字和姓氏觸發成observables。在下面的代碼中,我已經使用了主題,但是如果您能夠使用靜態Observable方法將它們「轉換」爲觀察對象,則會更好;例如Observable.FromEvent

然後轉動代碼將結果提取到可觀察對象中。在下面的代碼中,我使用了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); 
} 

快速提示:你應該傳遞一個明確的調度到油門,這樣就可以有效的單元測試使用TestScheduler此代碼。

希望它有幫助。

+0

對於您建議的所有主題代碼,'WhenAnyValue'方法是ReactiveUI的縮寫。 – Shlomo

+0

Hi @ibebbs。正如Shlomo指出的,我使用ReactiveUI來消除一些管道。通過寫出來,我真的很感激你的答案!有關調度程序的提示也是我將要看的內容。 – buckley

+0

看來,這是一本教科書示例http://www.introtorx.com/content/v1.0.10621.0/12_CombiningSequences.html#Switch – buckley

3

如果你把你的異步任務變成可觀察到的,這看起來像一個典型的用途爲Switch

this.WhenAnyValue(x => x.FirstName, 
        x => x.LastName 
    ) 
    .Throttle(TimeSpan.FromMilliseconds(100)) 
    .Select(l => PopulateGrid().ToObservable()) 
    .Switch() 
    .Subscribe(vm => Results = vm); 

Throttle應該被用來抑制當用戶鍵入電話。所以請隨時調整TimeSpan。

+0

啊切換是的。這是一個原始的,當我閱讀它可能是有用的,這可以適合。明天我會放棄它。 switch的定義:將可觀察序列的可觀察序列轉換爲可觀察序列,僅產生最近可觀察序列的值。 – buckley

相關問題