反应性扩展-属性相互更新 [英] Reactive Extensions - Properties update each other

查看:95
本文介绍了反应性扩展-属性相互更新的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有2个DecimalUpDown控件num_one和num_two,分别绑定到属性First和Second.更改第一"后,它将与服务器联系以计算第二"的值,反之亦然.触发服务器调用会异步释放UI,但是在快速触发(例如滚轮)时,最后一个请求并不总是最后一个返回,因此值可能不同步.

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

推荐答案

有一个内置的Rx运算符,它可以帮助您在不使用Throttle和超时的情况下准确地执行所需的操作-它是Switch运算符.

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.

Switch运算符不适用于IObservable<T>,因此大多数情况下,您永远不会在智能感知中看到它.

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

相反,它在IObservable<IObservable<T>>(可观察的数据流)上进行操作,并且通过连续切换到最新的可观察物(并忽略先前可观察物的任何值),将源扩展为IObservable<T>.它仅在外部可观测对象完成时完成,而不在内部可观测对象完成时完成.

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.

这是操作方法.

首先,我将yucky事件处理代码删除到几个可观察对象中.

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

您的代码似乎有些混乱.我假设DecimalUpDown控件的值是返回结果的服务器函数的输入.因此,这是将调用服务器的功能.

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

DistinctUntilChanged确保仅在值实际更改时才进行呼叫.

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

然后可以很容易地调用这两个服务器函数,执行Switch并仅获取最新的结果,然后将其分配给该属性.

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.

您可能需要在此处或此处弹出调度程序和ObserveOn才能将订阅移至UI线程,但是否则此解决方案应该可以很好地工作.

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.

这篇关于反应性扩展-属性相互更新的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆