Reactive Extensions - Properties update each other

只愿长相守 提交于 2019-12-05 00:05:17

问题


I have 2 DecimalUpDown controls, num_one and num_two, bound to properties First and Second respectively. When First is changed it will contact a server to calculate the value of Second, and vice-versa. Firing the server calls asynchronously freed the UI but, upon quick firing (scroll wheel for example), the last request isn't always the last to return so the values may become out of sync.

Using Reactive I'm trying to Throttle the calls to only fire the server call after the user has stopped making changes for a little while. The problem is that when you make a change during an update, the Properties changing start triggering each other and get stuck in back and forth depending on the TimeSpan of the 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");
        }
    }

回答1:


There's an inbuilt Rx operator that can help you do exactly what you want without using Throttle and timeouts - it's the Switch operator.

The Switch operator doesn't work on IObservable<T> so most times you would never see it in intellisense.

Instead it operates on IObservable<IObservable<T>> - a stream of observables - and it flattens the source to IObservable<T> by continually switching to the latest observable produced (and ignoring any values from the previous observables). It only completes when the outer observable completes and not the inner ones.

This is exactly what you want - if a new value change occurs then ignore any previous results and only return the latest one.

Here's how to do it.

First I removed the yucky event handling code into a couple of observables.

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

Your code seems to be a bit muddled. I assume that the value of the DecimalUpDown controls are inputs to the server function that returns the result. So here are the functions that will call the server.

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

Obviously you put in your actual server code calls in these two functions.

Now it is almost trivial to wire up the final observables and subscriptions.

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

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

The DistinctUntilChanged makes sure we only make the call if the values actually changed.

Then it's easy to call the two server functions, perform the Switch and get back only the latest result which is then just assigned to the property.

You may need to pop in a scheduler here or there and an ObserveOn to get the subscription over to the UI thread, but otherwise this solution should work nicely.




回答2:


Property changed notifications should not be fired if the property hasn't changed. You need to add an if statement to the properties.

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

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


来源:https://stackoverflow.com/questions/11247013/reactive-extensions-properties-update-each-other

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!