我很难找到在ViewModel中安排长时间运行的反应性属性“ getter”的正确方法。
This excerpt from Intro to RX准确地描述了我想做什么:
响应某种用户操作
在后台线程上工作
将结果传递回UI线程
更新用户界面
仅在这种情况下,除了用户交互之外,我还想对其他属性做出反应。
以下是我用来从原始属性中获取派生属性的通用模板(在实际代码中,有一系列层叠的派生属性)。
在Reactive ViewModel(继承自ReactiveObject)中,我已经具有一些从其他属性派生的属性。例如,当Original
更改时,将重新计算Derived
。
public TOriginal Original
{
get { return _original; }
set { this.RaiseAndSetIfChanged(ref _original, value); }
}
TOriginal _original;
public TDerived Derived { get { return _derived.Value; } }
readonly ObservableAsPropertyHelper<double[,]> _derived;
this.WhenAnyValue(x => x.Original)
.Where(originalValue => originalValue != null)
// ObserveOn? SubscribeOn? Which scheduler?
.Select(derivedValue => LongRunningCalculation(originalValue))
// Same thing here: ObserveOn? SubscribeOn? Which scheduler?
.ToProperty(this, x => x.Derived, out _derived); // should I use the `scheduler:` in this method?
我的问题是:我不知道如何将这些不同的“设计选择”结合起来以获得所需的响应式UI:
使用哪个调度程序?我看过
RxApp.TaskpoolScheduler
,RxApp.MainThreadScheduler
,NewThreadScheduler.Default
以及其他示例。何时使用甚至
SubscribeOn
的ObserveOn
vs ObserveOnDispatcher
或scheduler:
的ToProperty
参数?顺序会有所不同吗?我在
Select
运算符之前和之后都放置了重新计划方法,但是我不太确定。坦率地说,我不确定甚至还不需要Select
。我看到了一些将
Binding.IsAsync
设置为true
的示例,但是我尝试了一下,似乎并没有太大的区别,但是再次,也许是由于其他因素。SynchronizationContext
和ThreadPriority
的概念在这里是相对的吗?有没有办法在显示的代码中配置它们?我应该为此使用
ReactiveCommand
或其他ReactiveUI类吗?最令人不安的事实是,使用某些组合,计算可以正常运行,但会阻塞UI,而使用其他组合,则异步计算值,并且UI的阻塞程度有所降低,但有时会派生一部分值(例如,在项目集合中)不可用!
抱歉,如果我要求太多,但是我没有找到预期的权威方法来完成我在文档中需要的工作。
最佳答案
调度程序
在Rx.NET中,有一些调度程序,包括WPF专有的特殊调度程序。TaskPoolScheduler
在任务池上运行代码。这有点像在Task
中运行代码。NewThreadScheduler
产生一个新线程来运行代码。通常不要使用此运算符,除非您知道自己“需要”它(几乎永远不需要)DispatcherScheduler
在UI线程上运行代码。当您要在VM中设置属性时,请使用此选项
RxUI带来了两个与平台无关的调度程序抽象。无论您使用的是哪种平台(WPF,UWP,Xamarin.iOS,Xamarin.Android),RxApp.MainThreadScheduler
始终引用UI线程调度程序,而RxApp.TaskPoolScheduler
则引用类似于后台线程的内容。
如果要保持简单,只需使用RxApp
调度程序即可; RxApp.MainThreadScheduler
用于UI,而RxApp.TaskPoolScheduler
用于背景/重型。
观察/订阅
名称SubscribeOn()
有点混乱,因为它不会直接影响Subscribe()
方法。 SubscribeOn()
决定可观察对象将从哪个调度程序开始;原始/第一个订阅将在哪个调度程序上完成(Subscribe()
方法将在哪个调度程序上执行)。我想认为SubsribeOn()
将可观察的链向上移动到顶部,并确保可观察的链在给定的调度程序上产生值。
一些运算符让您指定它们应在哪个调度程序上运行。当他们这样做时,您应该始终喜欢通过一个调度程序,这样您就知道他们将在哪里工作,并防止他们潜在地阻塞UI主题(尽管他们不应该这样做)。 SubsribeOn()
是可观察变量的“ hack”,不允许您指定调度程序。如果使用SubscribeOn()
,但操作员指定了调度程序,则来自操作员的信号将在操作员调度程序上发出,而不是在SubscribeOn()
中指定的信号。ObserveOn()
的作用与SubscribeOn()
大致相同,但是它“从现在开始”执行。 ObserveOn()
之后的运算符和代码将在分配给ObserveOn()
的调度程序上执行。我想认为ObserveOn()
的意思是“将线程更改为此”。
做繁重的工作
如果要进行繁重的工作,请将其放入函数中并调用该函数,就像您对LongRunningCalculation()
所做的一样。您可以在ObserveOn(RxApp.TaskPoolScheduler)
之前放置Select()
,在其后放置ObserveOn(RxApp.MainThreadScheduler
,但我更喜欢将Observable.Start()
与SelectMany()
结合使用。Observable.Start()
基本上是功能的Observable.Return()
:“请将此功能的结果作为观察对象给我。”您还可以指定应调用该函数的调度程序。SelectMany()
确保我们得到可观察的结果,而不是可观察的本身。 (有点类似于await
的可观察对象:“在获得可观察对象的结果之前,请不要执行下一个运算符”)
派生属性
您正在正确执行派生属性。
使用WhenAnyValue()
获取属性更改并将其通过管道传输到ToProperty()
。您介于两者之间的运算符可能会在后台线程上起作用,这会延迟派生属性的设置,但这就是为什么要使用INotifyPropertyChanged
的原因。
我拿
这是我将如何实现您的特定示例的方法:
public TOriginal Original
{
get { return _original; }
set { this.RaiseAndSetIfChanged(ref _original, value); }
}
TOriginal _original;
public TDerived Derived { get { return _derived.Value; } }
readonly ObservableAsPropertyHelper<double[,]> _derived;
_derived = this.WhenAnyValue(x => x.Original)
.Where(originalValue => originalValue != null)
// Sepcify the scheduler to the operator directly
.SelectMany(originalValue =>
Observable.Start(
() => LongRunningCalculation(originalValue),
RxApp.TaskPoolScheduler))
.ObserveOn(RxApp.MainThreadScheduler)
// I prefer this overload of ToProperty, which returns an ObservableAsPropertyHelper
.ToProperty(this, x => x.Derived);
我们有一个用于ReactiveUI的Slack团队,欢迎您加入。您可以通过单击here来请求邀请
关于c# - 在派生属性中使用调度程序以具有响应UI的合适方法是什么?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45146479/