【问题标题】:Reactive Extensions - Properties update each other反应式扩展 - 属性相互更新
【发布时间】:2012-06-30 02:42:54
【问题描述】:

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

【问题讨论】:

    标签: c# asynchronous system.reactive


    【解决方案1】:

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

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

    【讨论】:

      【解决方案2】:

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

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

      相反,它在IObservable&lt;IObservable&lt;T&gt;&gt;(可观察数据流)上运行,并通过不断切换到产生的最新可观察数据(并忽略先前可观察数据的任何值)将源扁平化为IObservable&lt;T&gt;。它仅在外部 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 线程,否则此解决方案应该可以正常工作。

      【讨论】:

      • 太好了,谢谢。有没有办法限制服务器调用,以免发送大量请求?我试了一下,但遇到了他们“打架”的同样问题。
      • 在 .Select(x => serverCall()) 之前添加一个 .Sample(xxx) 似乎可以解决问题?
      • @MimHufford - 看看这个答案:stackoverflow.com/a/7604825/259769。它有一个BufferWithInactivity 扩展方法,可以缓冲超时。将它与Switch 一起使用,它将为您提供最佳解决方案。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-05-15
      • 1970-01-01
      • 1970-01-01
      • 2017-06-12
      • 1970-01-01
      相关资源
      最近更新 更多