2012-06-28 99 views
4

我有2所DecimalUpDown控制,num_one和num_two,分別綁定到性能第一和第二。當First被更改時,它將聯繫服務器來計算Second的值,反之亦然。觸發服務器調用異步釋放UI,但在快速觸發時(例如滾動輪),最後一個請求並不總是最後一個返回,所以這些值可能會失去同步。無擴展 - 屬性更新對方

利用無我想油門調用只有用戶已經停止生產了一小會兒更改後觸發服務器調用。問題在於,在更新期間進行更改時,屬性更改開始會相互觸發並根據Throttle的TimeSpan來回卡住。

public MainWindow() 
    { 
     InitializeComponent(); 

     DataContext = this; 

     Observable.FromEventPattern<RoutedPropertyChangedEventHandler<object>, RoutedPropertyChangedEventArgs<object>>(h => num_one.ValueChanged += h, h => num_one.ValueChanged -= h) 
      .Throttle(TimeSpan.FromMilliseconds(100), Scheduler.ThreadPool) 
      .Subscribe(x => 
      { 
       Thread.Sleep(300); // simulate work 
       Second = (decimal)x.EventArgs.NewValue/3.0m; 
      }); 

     Observable.FromEventPattern<RoutedPropertyChangedEventHandler<object>, RoutedPropertyChangedEventArgs<object>>(h => num_two.ValueChanged += h, h => num_two.ValueChanged -= h) 
      .Throttle(TimeSpan.FromMilliseconds(100), Scheduler.ThreadPool) 
      .Subscribe(x => 
      { 
       Thread.Sleep(300); // simulate work 
       First = (decimal)x.EventArgs.NewValue * 3.0m; 
      }); 
    } 

    private decimal first; 
    public decimal First 
    { 
     get { return first; } 
     set 
     { 
      first = value; 
      NotifyPropertyChanged("First"); 
     } 
    } 

    private decimal second; 
    public decimal Second 
    { 
     get { return second; } 
     set 
     { 
      second = value; 
      NotifyPropertyChanged("Second"); 
     } 
    } 

回答

6

有一個內置的Rx操作,可以幫助你做你想要什麼,而無需使用Throttle和超時 - 這是Switch操作。

Switch運算符不能在IObservable<T>上運行,所以絕大多數時候你都不會在intellisense中看到它。

取而代之的是它在IObservable<IObservable<T>>上運行 - 一系列可觀測量 - 通過不斷切換到最新的可觀測值(並忽略先前觀測值中的任何值),它將源平整爲IObservable<T>。它只在外部可觀察者完成而不是內部完成時完成。

這正是你想要的 - 如果一個新值發生變化時則忽略任何以前的結果,只返回最新的一個。

這裏是如何做到這一點。

首先我刪除了令人討厭的事件處理代碼轉換爲一對夫婦觀測的。

var ones = 
    Observable 
     .FromEventPattern< 
      RoutedPropertyChangedEventHandler<object>, 
      RoutedPropertyChangedEventArgs<object>>(
      h => num_one.ValueChanged += h, 
      h => num_one.ValueChanged -= h) 
     .Select(ep => (decimal)ep.EventArgs.NewValue); 

var twos = 
    Observable 
     .FromEventPattern< 
      RoutedPropertyChangedEventHandler<object>, 
      RoutedPropertyChangedEventArgs<object>>(
      h => num_two.ValueChanged += h, 
      h => num_two.ValueChanged -= h) 
     .Select(ep => (decimal)ep.EventArgs.NewValue); 

你的代碼似乎有點混亂。我假定DecimalUpDown控件的值是返回結果的服務器函數的輸入。所以這裏是調用服務器的函數。

Func<decimal, IObservable<decimal>> one2two = x => 
    Observable.Start(() => 
    { 
     Thread.Sleep(300); // simulate work 
     return x/3.0m; 
    }); 

Func<decimal, IObservable<decimal>> two2one = x => 
    Observable.Start(() => 
    { 
     Thread.Sleep(300); // simulate work 
     return x * 3.0m; 
    }); 

顯然你把你的實際服務器代碼調用放在這兩個函數中。

現在它幾乎是微不足道的線了最後的觀測和訂閱。

ones 
    .DistinctUntilChanged() 
    .Select(x => one2two(x)) 
    .Switch() 
    .Subscribe(x => 
    { 
     Second = x; 
    }); 

twos 
    .DistinctUntilChanged() 
    .Select(x => two2one(x)) 
    .Switch() 
    .Subscribe(x => 
    { 
     First = x; 
    }); 

DistinctUntilChanged確保我們只在值實際發生變化時才進行調用。

然後可以很容易地調用這兩個服務器的功能,執行Switch並取回唯一的,然後只是賦予屬性的最新結果。

您可能需要在這裏或那裏彈出調度程序,並且需要ObserveOn才能將訂閱傳遞到UI線程,否則此解決方案應該很好地工作。

+0

這是完美的,謝謝你。有什麼辦法來限制服務器調用,以便大量的請求不被髮送?我有一個去,但碰到了他們'對戰'相同的問題。 –

+0

在.Select(x => serverCall())之前添加一個.Sample(xxx)似乎有訣竅? –

+0

+1非常好解釋。 – Asti

1

如果屬性未更改,則不應觸發屬性更改的通知。您需要將if語句添加到屬性。

public decimal First 
{ 
    get { return first; } 
    set 
    { 
     if(first == value) 
      return; 

     first = value; 
     NotifyPropertyChanged("First"); 
    } 
}