Почему Observable.range зависает при запуске в потоке GUI? - PullRequest
2 голосов
/ 08 апреля 2020
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

Я пытаюсь сделать небольшое доказательство библиотеки понятий для реактивных интерфейсов, и я столкнулся с этой проблемой, когда пытался написать функцию, которая добавляет более одного элемента управления к родителю. Стандартная настройка свойств работает, когда они являются синглетонами, но не при использовании функций типа Observable.range, которые являются итераторами.

Возможно ли сделать эту работу?

Поскольку F # необходимо вручную добавить некоторые вещи в файл проекта, чтобы можно было использовать WPF, здесь - репозиторий для этого.

1 Ответ

3 голосов
/ 08 апреля 2020

Планировщик по умолчанию для Range имеет значение Scheduler.CurrentThread.

CurrentThread и Immediate имеют поведение, которое иногда приводит к вечному батуту или тупику, особенно при попытке синхронного использования с Observable.Create или аналогичные незапланированные холодные наблюдаемые.

Точные причины, по которым они блокируются, трудно описать, но они похожи на поведение здесь и здесь .

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

Вышеуказанное распоряжение никогда не может быть вызвано, чтобы предотвратить выброс предметов - до после того, как предметы были выброшены. Если вы вручную создаете наблюдаемую, попробуйте заставить ее принять аргумент планировщика.

DispatcherScheduler вернувшись в ядро ​​Rx. Вот реализация минимального 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"
    }
...