I'm trying to launch a window which will require waiting for a login to an external system, so I would like to handle this login asynchronously. I've achieved this in the F# interactive window and everything behaves as expected, however when I run the code in my program I get the error:
System.InvalidOperationException: The calling thread cannot access this object because a different thread owns it.
F# Interactive:
#r "WindowsBase"
#r "PresentationCore"
#r "PresentationFramework"
open System
open System.Threading
open System.Windows
let loginTask = async {
Console.WriteLine "Logging in"
Thread.Sleep(5000)
let user = "MyUser"
Console.WriteLine ("Logged in as " + user)
return user
}
let createWindow () =
Console.WriteLine "Creating window"
let window = Window()
window.Title <- "MyWindow"
window.Show()
window
let runWindowWithUser (window:Window) user =
window.Title <- (user + "'s Window")
Console.WriteLine ("Running " + window.Title + " as " + user)
let mainAsync = async {
let window = createWindow()
let! userToken = loginTask |> Async.StartChild
let! user = userToken
runWindowWithUser window user
}
do mainAsync |> Async.StartImmediate
Program.fs
open System.Threading
open System.Windows
[<EntryPoint; STAThread>]
let main argv =
let loginTask = async {
Console.WriteLine "Logging in"
Thread.Sleep(5000)
let user = "MyUser"
Console.WriteLine ("Logged in as " + user)
return user
}
let createWindow () =
Console.WriteLine "Creating window"
let window = Window()
window.Title <- "MyWindow"
window.Show()
window
let runWindowWithUser (window:Window) user =
window.Title <- (user + "'s Window")
Console.WriteLine ("Running " + window.Title + " as " + user)
let mainAsync = async {
let window = createWindow()
let! userToken = loginTask |> Async.StartChild
let! user = userToken
runWindowWithUser window user
}
do mainAsync |> Async.StartImmediate
Console.ReadKey()
1
I understand that let!
can cause the rest of the workflow to continue on the background thread so I have tried swapping threads:
let mainAsync = async {
let context = SynchronizationContext.Current
let window = createWindow()
do! Async.SwitchToThreadPool()
let! user = loginTask
do! Async.SwitchToContext context
runWindowWithUser window user
}
but this doesn't seem to change back to the original thread as I'm expecting.
I also tried to keep all of my UI code out of the async workflow to avoid dealing with threads, but then I'm unsure of how to get my user
information back from work which is done on a background thread
let loginTask = async {
Console.WriteLine "Logging in"
Thread.Sleep(5000)
let user = "MyUser"
Console.WriteLine ("Logged in as " + user)
return user
}
Console.WriteLine "Creating window"
let window = Window()
window.Title <- "MyWindow"
window.Show()
let user = loginTask|> Async.StartImmediate // How do I get user information from loginTask without using let! that must be called from an async workflow?
window.Title <- (user + "'s Window")
Console.WriteLine ("Running " + window.Title + " as " + user)
I'm very new to F# and functional programming in general. How am I able to get the user
information from the login code on a background thread into the UI thread, and also why is the threading behavior different in the F# interactive window?
Here's my code, I've removed the Console.Writeline() calls, you would just replace those with logger calls (or Debug.WriteLine(), or whatever).
open System
open System.Threading
open System.Windows
let createWindow() =
let window = Window()
window.Title <- "MyWindow"
window.Show()
window
let runWindowWithUser (window: Window) user =
window.Title <- (user + "'s Window")
let loginTask =
async {
Thread.Sleep(1000)
let user = "MyUser"
return user
}
[<EntryPoint; STAThread>]
let main _ =
let app = Application()
app.MainWindow <- createWindow()
app.Startup.Add (fun _ ->
async {
let context = SynchronizationContext.Current
do! Async.SwitchToThreadPool()
let! user = loginTask
do! Async.SwitchToContext context
runWindowWithUser app.MainWindow user
} |> Async.StartImmediate
)
app.Run()