2012-06-28 2 views
4

У меня есть два элемента управления DecimalUpDown, num_one и num_two, привязанные к свойствам First и Second соответственно. Когда First изменяется, он свяжется с сервером для вычисления значения Second и наоборот. Увольнение сервера вызывает асинхронное освобождение пользовательского интерфейса, но при быстрой стрельбе (например, колесо прокрутки) последний запрос не всегда является последним для возврата, поэтому значения могут перестать синхронизироваться.Реактивные расширения - Свойства обновляются друг друга

Использование Reactive Я пытаюсь отключить вызовы только для того, чтобы только запустить вызов сервера после того, как пользователь прекратил внесение изменений на некоторое время. Проблема заключается в том, что когда вы делаете изменения во время обновления, изменение свойств запуска запускает друг друга и застревает взад и вперед в зависимости от TimeSpan от Throttle.

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>, постоянно переключаясь на последние наблюдаемые произведенные (и игнорируя любые значения из предыдущих наблюдаемых). Он завершается только тогда, когда внешнее наблюдаемое завершается, а не внутренним.

Это именно то, что вы хотите - если произойдет новое изменение значения, то проигнорируйте любые предыдущие результаты и верните только последний.

Вот как это сделать.

Сначала я удалил код обработки событий yucky в пару наблюдаемых.

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; 
    }); 

The DistinctUntilChanged убеждается мы только сделать вызов, если значения на самом деле изменилось.

После этого легко вызвать две функции сервера, выполнить Switch и получить только последний результат, который затем просто присваивается свойству.

Возможно, вам понадобится указать планировщик здесь или там и ObserveOn, чтобы получить подписку на поток пользовательского интерфейса, но в противном случае это решение должно работать хорошо.

+0

Это замечательно, спасибо.Есть ли способ дросселировать вызовы сервера, чтобы не отправлять много запросов? Я пошел, но столкнулся с той же проблемой, что они «сражались» друг с другом. –

+0

Добавление параметра .Sample (xxx) перед .Select (x => serverCall()), похоже, делает трюк? –

+0

+1 Очень красиво объяснено. – Asti

1

Измененные изменения свойств не должны быть уволены, если имущество не изменилось. Вам нужно добавить инструкцию if к свойствам.

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

     first = value; 
     NotifyPropertyChanged("First"); 
    } 
} 
Смежные вопросы