Sunday, 6 May 2012

How do I spawn a WPF UI from F# interactive asynchronously?


F# interactive is a fantastic tool for interactive technical computing and one of the first things many users want is the ability to fire up WPF, create new windows and control the content in them from the F# interactive session. This blog post describes one solution.
Firstly, write a lazy thunk that initializes WPF (including an STA UI thread and Application) when its evaluation is forced:
> let ui =
    let mk() =
      let wh = new ManualResetEvent(false)
      let application = ref null
      let start() =
        let app = Application()
        application := app
        ignore(wh.Set())
        app.Run() |> ignore
    let thread = Thread start
    thread.IsBackground <- true
    thread.SetApartmentState ApartmentState.STA
    thread.Start()
    ignore(wh.WaitOne())
    !application, thread
  lazy(mk());;
val ui : Lazy<Application * Thread> = <unevaluated>
Then write a spawn function that dispatches the application of a function f to an argument x such that it is run on the UI thread:
> let spawn : ('a -> 'b) -> 'a -> 'b =
    fun f x ->
      let app, thread = ui.Force()
      let f _ =
        try
          let f_x = f x
          fun () -> f_x
        with e ->
          fun () -> raise e
      let t = app.Dispatcher.Invoke(DispatcherPriority.Send, System.Func<_, _>(f), null)
      (t :?> unit -> 'b)();;
val spawn : ('a -> 'b) -> 'a -> 'b
This technique is described in detail in the book Visual F# 2010 for Technical Computing. and is the foundation of the F# for Visualization library.

1 comment:

Hotel Siam said...

Hi,

I am trying to use this approach, and it works great from the FSI the first time I run the function, but the second time I run it I get the following error:

System.NullReferenceException: Object reference not set to an instance of an object.
at Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicFunctions.UnboxGeneric[T](Object source)
at FSI_0007.Test.UI.spawn[a,b](FSharpFunc`2 f, a x) in C:\Repos\Test.UI\ui.fs:line 40
at .$FSI_0012.main@()
Stopped due to error

The trace is pointing to the following line in the spawn function as the source of the error:

let t = app.Dispatcher.Invoke(DispatcherPriority.Send, System.Func<_, _>(f), null)

Any ideas as to what might be going on here? I'd appreciate any help you can give, as there isn't a lot on the web dealing with this kind of issue.