asynchronousf#fsxaml

Progress bar with async in FsXaml application


In my F# (FsXaml/Code Behind) application, I would like to use a progress bar without the utilisation of a background worker as I do in C#. Based on an article on the internet (the link is here), I tried to use asynchronous workflows.

I created code based (to some extend) on the examples in the aforementioned article, but it did not work as I had expected. The current thread (UI thread) is still blocked as if no async code was there. No switching to a background thread occurs. The progress bar is activated only after the long running operation has been finished. Removing the onThreadPool function has no effect.

My question is: What is wrong in my code and how to make it right?

type MainWindowXaml = FsXaml.XAML<"XAMLAndCodeBehind/MainWindow.xaml">

type MainWindow() as this =

    inherit MainWindowXaml()

    //....some code....

    let ButtonClick _ = 
   
        //....some code....
       
        let longRunningOperation() = //....some long running operation (reading from Google Sheets)....            
             
        let progressBar() = this.ProgressBar.IsIndeterminate <- true     

        let doBusyAsync progress operation =  
            progress
            async
                {   
                  do! operation
                }
            |> Async.StartImmediate 
    
        let onThreadPool operation =
            async
                {    
                  let context = System.Threading.SynchronizationContext.Current
                  do! Async.SwitchToThreadPool()
                  let! result = operation
                  do! Async.SwitchToContext context
                  return result
                } 
    
        let asyncOperation progress operation =   
            async { operation } 
            |> onThreadPool
            |> doBusyAsync progress 
    
        (progressBar(), longRunningOperation()) ||> asyncOperation 
      
    do
        //....some code....
        this.Button.Click.Add ButtonClick

Solution

  • There are a number of things wrong with your code.

    In summary, your code somehow got way too complex. You should simplify it to something very basic as the first step. I do not have the right setup to try this, but I think something like the following should do the trick:

    let ButtonClick _ = 
      let longRunningOperation() = 
        // some long-running operation
    
      let asyncOperation() = async {
        // Start the progress bar here
        let context = System.Threading.SynchronizationContext.Current
        do! Async.SwitchToThreadPool()
        let result = longRunningOperation()
        do! Async.SwitchToContext context
        // Display the 'result' in your user interface
        // Stop the progress bar here
      }
    
      Async.Start(asyncOperation)
    

    I removed all useless functions and parameter passing and simplified it as much as possible - it is just your long running operation, which is called directly from async once it switches to thread pool. You get your result and, after switching back to the original context, should be able to display that in your user interface. Ideally, you'd make the longRunningOperation itself asynchronous (and call it using let!) but the above should work.