简体   繁体   中英

F# Making the result of an async workflow available to be displayed by the UI thread

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()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM