【问题标题】:Why does Observable.range freeze when run on the GUI thread?为什么 Observable.range 在 GUI 线程上运行时会冻结?
【发布时间】:2020-07-20 16:02:39
【问题描述】:
module StackTenButtons.Try2

open System
open System.Windows
open System.Windows.Controls

open System.Reactive.Linq
open System.Reactive.Disposables
open FSharp.Control.Reactive

let control c l = 
    Observable.Create (fun (sub : IObserver<_>) ->
        let c = c()
        let d = new CompositeDisposable()
        List.iter (fun x -> d.Add(x c)) l
        sub.OnNext(c)
        d :> IDisposable
        )
let do' f c = f c; Disposable.Empty
let prop s v c = Observable.subscribe (s c) v

let w =
    control Window [
        prop (fun t v -> t.Content <- v) <| control StackPanel [
            do' (fun pan ->
                Observable.range 0 10
                |> Observable.subscribe (fun x -> pan.Children.Add(Button(Content=sprintf "Button %i" x)) |> ignore)
                |> ignore
                )
            ]
        ]

[<STAThread>]
[<EntryPoint>]
let main _ = w.Subscribe (Application().Run >> ignore); 0

我正在尝试为反应式 UI 制作一个小型概念证明库,并且在尝试编写向父级添加多个控件的函数时遇到了这个问题。标准属性设置在它们是单例时有效,但在使用像 Observable.range 这样的迭代器时无效。

是否有可能做到这一点?

由于 F# 需要手动将一些东西添加到项目文件中,以便可以使用 WPF,here is 的 repo。

【问题讨论】:

  • 当我将Observable.range 0 10 更改为Observable.rangeOn Scheduler.Immediate 0 10 时它可以工作。我遇到的一件事是找出DispatcherScheduler 在.NET Core 3.1 中消失的位置。我不确定我应该用什么代替它。我不一定想要Scheduler.Immediate 来处理所有事情,所以拥有它会很好。
  • 与其创建控件,不如假设我从某处异步获取资源。我如何在 UI 线程上观察它?
  • 如果我这样做 Observable.rangeOn ThreadPoolScheduler.Instance 0 10 给我 System.InvalidOperationException 因为只有 UI 线程可以改变 UI 控件。 Observable.rangeOn ThreadPoolScheduler.Instance 0 10 |&gt; Observable.observeOn Scheduler.Immediate 也给了我同样的例外。

标签: wpf f# system.reactive


【解决方案1】:

Range 的默认调度程序是 Scheduler.CurrentThread。

CurrentThread 和Immediate 的行为有时会导致永久蹦床或死锁,特别是在尝试与Observable.Create 或类似的未计划的冷可观察对象同步使用时。

它们锁定的确切原因很难描述,但与here 和here 发现的行为相似。

 Observable.Create (fun (sub : IObserver<_>) -> 
        sub.OnNext(1)
        sub.OnNext(2)
        sub.OnNext(3)
        d :> IDisposable //<-- this dispose should cancel all `OnNext`
 )

上面的 dispose 永远不能被调用,以防止项目被发射 - 直到项目被发射之后。如果你手动构造一个 observable,试着让它接受一个调度器参数。

DispatcherScheduler 回到 Rx.NET 核心可能还有一段路要走。 这是一个最小的DispatcherScheduler 的实现:

type DispatcherScheduler =
    static member Instance = {
     new IScheduler with
         member _.Now = DateTimeOffset.Now
         member _.Schedule<'S>(state: 'S, action: Func<IScheduler, 'S, IDisposable>) : IDisposable = 
            let op = Application.Current.Dispatcher.InvokeAsync(fun () -> action.Invoke(DispatcherScheduler.Instance, state))
            Disposable.Create(fun () -> op.Abort() |> ignore)
         member _.Schedule<'S>(state: 'S, dueTime: TimeSpan, action: Func<IScheduler, 'S, IDisposable>) : IDisposable = failwith "Not Impl"
         member _.Schedule<'S>(state: 'S, dueTime: DateTimeOffset, action: Func<IScheduler, 'S, IDisposable>) : IDisposable = failwith "Not Impl"
    }

【讨论】:

  • ``` let a = Application();使用 __ = w.Subscribe (fun w -> a.MainWindow Scheduler.Immediate 传递给range 变得不必要了。但现在我被困在如何在 UI 线程上观察。知道如何在 .NET Core 3.1 上获取调度程序调度程序吗?
  • AFAIK,没有为 .Net Core 3 实现的调度程序。
  • 但是github.com/microsoft/WPF-Samples/blob/… 在项目文件中使用了一个`true`。如果您可以在设置此标志的情况下添加System.Windows.Threading,您可以试试吗?
  • 我已经不得不使用&lt;Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"&gt; 和&lt;UseWpf&gt;true&lt;/UseWpf&gt;。 DispatcherScheduler 即使在该命名空间中,它似乎也是 Rx 包的一部分,但它不再是。尽管如此,即使那件事不存在,但在 UWP 和 Xamarin 中如何观察 UI 线程。必须有某种方法来做到这一点。我发现的一种方法是使用Application 实例的Dispatcher。以Observable.subscribe (fun x -&gt; dis.Invoke (fun () -&gt; pan.Children.Add(Button(Content=sprintf "Button %i" x)) |&gt; ignore)) 为例。
  • 我可以将它作为这样的参数传递并调用它。不过我不能ObserveOn它。
猜你喜欢
  • 2014-09-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-12
  • 1970-01-01
  • 2011-01-28
相关资源
最近更新 更多