在派生属性中使用调度程序以获得响应式 UI 的适当方法是什么?

hel*_*ker 4 c# wpf asynchronous scheduler reactiveui

我很难找到在我的 ViewModel 中安排长时间运行的反应性属性“getter”的正确方法。

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?
Run Code Online (Sandbox Code Playgroud)

我的问题是:我不知道应该如何组合这些不同的“设计选择”来获得我想要的响应式 UI:

  • 使用哪个调度程序?我已经看到了例子RxApp.TaskpoolScheduler,RxApp.MainThreadScheduler,NewThreadScheduler.Default,以及其它可能的。
  • 当使用SubscribeOnVSObserveOn甚至ObserveOnDispatcher还是scheduler:参数ToProperty?
  • 顺序有区别吗?我已经把重新调度的方法放在Select操作员之前和之后,但我不太确定。Select坦率地说,我不确定甚至需要。
  • 我见过一些例子设置Binding.IsAsync到true,但我尝试过了,没有显得太大的区别,但同样,或许是因为其他因素。
  • SynchronizationContext和ThreadPriority相关的概念在这里吗?有没有办法在显示的代码中配置它们?
  • 我应该ReactiveCommand为此使用或其他一些 ReactiveUI 类吗?

最令人伤脑筋的事实是,对于某些组合,计算正常运行,但会阻塞 UI,而对于其他一些组合,值是异步计算的,UI 的阻塞较少,但有时会部分导出值(例如,在项目集合中)不可用!

对不起,如果我要求太多,但我没有找到任何权威的预期方法来做我在文档中需要的东西。

Jon*_*dle 6

调度器

在 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()决定 observable 将在哪个调度器上启动;原始/第一个订阅将在哪个调度程序上完成(而不是该Subscribe()方法将在哪个调度程序上执行)。我喜欢认为SubsribeOn()将 observable 链向上移动到顶部,并确保 observable 在给定的调度程序上产生值。

一些操作符让你指定他们应该在哪个调度程序上运行。当他们这样做时,你应该总是更喜欢传递一个 scheduler,这样你就知道他们要去哪里工作并防止他们潜在地阻塞 UI thead(尽管他们不应该)。SubsribeOn()对 observable 来说是一种“黑客”,它不允许您指定调度程序。如果您使用SubscribeOn(),但运算符指定了调度程序,则来自运算符的信号将在运算符调度程序上发出,而不是您在 中指定的调度程序SubscribeOn()。

ObserveOn()与 几乎相同SubscribeOn(),但它“从现在开始”。后面的运算符和代码ObserveOn()将在给定的调度程序上执行ObserveOn()。我喜欢认为这ObserveOn()意味着“将线程更改为这个”。

做繁重的工作

如果您要进行繁重的工作,请将其放入一个函数中并调用该函数,就像您对LongRunningCalculation(). 你可以在它ObserveOn(RxApp.TaskPoolScheduler)之前Select()和ObserveOn(RxApp.MainThreadScheduler之后使用 put an ,但我更喜欢Observable.Start()结合使用SelectMany()。

Observable.Start()基本上是Observable.Return()用于函数:“给我这个函数的结果作为一个可观察的。” 您还可以指定它应该调用该函数的调度程序。

SelectMany()确保我们得到 observable 的结果,而不是 observable 本身。(这有点像awaitfor observables:“在我们得到这个 observable 的结果之前不要执行这个 next 操作符”)

派生属性

您正在正确执行派生属性。

使用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);
Run Code Online (Sandbox Code Playgroud)

我们有一个 ReactiveUI 的 Slack 团队,欢迎您加入。您可以通过单击此处请求邀请