我有 2 个 DecimalUpDown 控件,num_one 和 num_two,分别绑定到属性 First 和 Second。当第一个被更改时,它将联系服务器来计算第二个的值,反之亦然。异步触发服务器调用会释放 UI,但在快速触发(例如滚轮)时,最后一个请求并不总是最后一个返回的请求,因此值可能会变得不同步。
使用反应式,我试图限制调用,以便在用户停止进行更改一段时间后仅触发服务器调用。问题是,当您在更新期间进行更改时,属性更改会开始相互触发,并根据油门的时间跨度来回卡住。
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
运算符。
Switch
运算符不适用于IObservable<T>
因此大多数时候您永远不会在智能感知中看到它。
相反,它对IObservable<IObservable<T>>
(可观察量流)进行操作,并通过不断切换到生成的最新可观察量(并忽略先前可观察量中的任何值)将源展平为IObservable<T>
。它仅在外部可观察量完成时完成,而不是内部可观察量完成。
这正是您想要的 - 如果发生新的值更改,则忽略任何先前的结果,仅返回最新的结果。
这是如何做到的。
首先,我将讨厌的事件处理代码删除到几个可观察量中。
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;
});
DistinctUntilChanged
确保我们仅在值实际更改时才进行调用。
然后,可以轻松调用两个服务器函数,执行Switch
并仅返回最新结果,然后将其分配给属性。
您可能需要在此处或那里弹出一个计划程序和一个ObserveOn
才能将订阅转到 UI 线程,但除此之外,此解决方案应该可以很好地工作。
如果属性未更改,则不应触发属性更改通知。您需要向属性添加 if 语句。
public decimal First
{
get { return first; }
set
{
if(first == value)
return;
first = value;
NotifyPropertyChanged("First");
}
}