2
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 

I am trying to make a small proof of concept library for reactive UIs and I've encountered this problem when trying to write a function that adds more than a single control to the parent. Standard property setting works when they are singletons, but not when using functions like Observable.range which are iterators.

Is it possible to make this work?

As F# needs some stuff to be added manually to the project file so WPF can be used, here is the repo for this.

3
  • It works when I change Observable.range 0 10 to Observable.rangeOn Scheduler.Immediate 0 10. One thing I am having trouble is figuring out where the DispatcherScheduler disappeared in .NET Core 3.1. I am not sure what I should substitute it with. I do not necessarily want to Scheduler.Immediate for everything so it would be good to have it. Commented Apr 8, 2020 at 13:31
  • Rather than creating a control, suppose I was getting a resource from somewhere asynchronously. How would I observe it on the UI thread? Commented Apr 8, 2020 at 13:41
  • If I do Observable.rangeOn ThreadPoolScheduler.Instance 0 10 that gives me the System.InvalidOperationException because only the UI thread can mutate the UI control. Observable.rangeOn ThreadPoolScheduler.Instance 0 10 |> Observable.observeOn Scheduler.Immediate also gives me the same exception. Commented Apr 8, 2020 at 13:46

1 Answer 1

3

The default scheduler for Range is Scheduler.CurrentThread.

CurrentThread and Immediate have behavior that sometimes results in a perpetual trampoline or a deadlock particularly when attempting to be used synchronously with an Observable.Create or similar un-scheduled cold observables.

The exact reasons why they lock up are difficult to describe, but are similar to the behavior found here and here.

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

The above dispose can never be called, to prevent items from being emitted - until after the items have been emitted. If you manually construct an observable, try to make it take in a scheduler argument.

DispatcherScheduler being back in Rx.NET core might still be some ways off. Here's an implementation of a minimal 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" } 
Sign up to request clarification or add additional context in comments.

10 Comments

``` let a = Application(); use __ = w.Subscribe (fun w -> a.MainWindow <- w; w.Show()); a.Run() ``` One way of getting out the deadlock is to start the program like this. Then passing Scheduler.Immediate to range becomes unnecessary. But now I am stuck on how to observe on the UI thread. Any idea how to get the dispatcher scheduler on .NET Core 3.1?
AFAIK, there isn't a dispatcher wasn't implemented for .Net Core 3.
But github.com/microsoft/WPF-Samples/blob/… has them using a ` <UseWpf>true</UseWpf>` in the project file. Can you try if you can add System.Windows.Threading with this flag set?
I already had to use <Project Sdk="Microsoft.NET.Sdk.WindowsDesktop"> and <UseWpf>true</UseWpf>. The DispatcherScheduler even though it is in that namespace seems to have been a part of the Rx package, but it is not any longer. Still, even though that thing is not there just how is observing on the UI thread done in UWP and Xamarin. There has to be some way of doing it. One way that I've found is to use the Application instance's Dispatcher. Observable.subscribe (fun x -> dis.Invoke (fun () -> pan.Children.Add(Button(Content=sprintf "Button %i" x)) |> ignore)) for example.
I can pass it as an argument like that and invoke it. I can't ObserveOn it though.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.