5

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

使用 Reactive 我试图限制调用以仅在用户停止更改一段时间后触发服务器调用。问题是当您在更新期间进行更改时,属性更改开始相互触发并根据油门的时间跨度来回卡住。

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

2 回答 2

7

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

Switch运算符不起作用,因此IObservable<T>大多数时候您永远不会在智能感知中看到它。

取而代之的是,它在一个可观察对象流上运行,并通过不断切换到最新产生的可观察对象(并忽略先前可观察对象的任何值)来IObservable<IObservable<T>>将源扁平化。IObservable<T>它仅在外部 observable 完成而不是内部 observable 完成时完成。

这正是您想要的 - 如果发生新的值更改,则忽略任何以前的结果并只返回最新的结果。

这是如何做到的。

首先,我将令人讨厌的事件处理代码删除到几个可观察对象中。

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

显然,您在这两个函数中放入了实际的服务器代码调用。

现在连接最终的 observables 和订阅几乎是微不足道的。

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 线程,否则这个解决方案应该可以很好地工作。

于 2012-06-29T00:06:48.250 回答
1

如果属性未更改,则不应触发属性更改通知。您需要在属性中添加 if 语句。

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

        first = value;
        NotifyPropertyChanged("First");
    }
}
于 2012-06-28T18:48:20.780 回答