我很难找到在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.TaskpoolSchedulerRxApp.MainThreadSchedulerNewThreadScheduler.Default以及其他示例。
何时使用甚至SubscribeOnObserveOn vs ObserveOnDispatcherscheduler:ToProperty参数?
顺序会有所不同吗?我在Select运算符之前和之后都放置了重新计划方法,但是我不太确定。坦率地说,我不确定甚至还不需要Select
我看到了一些将Binding.IsAsync设置为true的示例,但是我尝试了一下,似乎并没有太大的区别,但是再次,也许是由于其他因素。
SynchronizationContextThreadPriority的概念在这里是相对的吗?有没有办法在显示的代码中配置它们?
我应该为此使用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/

10-09 04:10