Home > Enterprise >  Progress bar with async in FsXaml application
Progress bar with async in FsXaml application

Time:11-28

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?

As Stackoverflow discourages thank-you comments, I hereby thank anybody for their answers in advance.

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

CodePudding user response:

There are a number of things wrong with your code.

  • First, in progressBar(), longRunningOperation() you actually call the long running operation and so it all gets run here. (As far as I can guess from your incomplete sample, this is just a function call, not another asynchronous operation).

  • You then pass the results operation and progress around, but those are just unit values that do not actually do anything.

  • Consequently, the asynchronous operation async { operation } that you pass to onThreadPool does not do anything at all.

  • In doBusyAsync, you use Async.StartImmediate to run the operation in a blocking way (so this would block the thread, even if it was running some actual operation).

  • Aside from being blocking, you also do not need async { do! operation } because this is equivalent to just operation.

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.

CodePudding user response:

To sum things up, I have extended Tomáš Petříček's code with code related to the long running operation based on Jim Foye's comment (about hopping back on the UI thread). The code now works like a charm. My thanks to Tomáš Petříček for his kind and detailed answer.

    let low = string (this.TextBox2.Text)
    let high = string (this.TextBox3.Text)
    let path = string (this.TextBox4.Text)

    (* longRunningOperation() replaced by textBoxString4() and textBoxString3() 
       based on the comment by Jim Foye
    
    let longRunningOperation() = 
        async
            {
              match textBoxString4 low high >= 0 with
              | false -> this.TextBox1.Text <- textBoxString3 low high path 
              | true  -> this.TextBox1.Text <- "Chybný rozdíl krajních hodnot"        
            }
    *)

    let textBoxString4() = 
        async
            {
              let result = textBoxString4 low high
              return result
            }                  
                           
    let textBoxString3() =        
        async
            {
              //the actual long running operation (reading data 
              //from Google Sheets)
              let result = textBoxString3 low high path 
              return result
            }  

    let asyncOperation() = 
        async
            {
              let context = System.Threading.SynchronizationContext.Current
              this.ProgressBar2.IsIndeterminate <- true
              do! Async.SwitchToThreadPool()
              (*let! result = longRunningOperation() throws an exception 
              "The calling thread cannot access this object because
               a different thread owns it." 
              *)
              let! result4 = textBoxString4()  
              let! result3 = textBoxString3()  
              do! Async.SwitchToContext context
              match result4 >= 0 with
              | false -> this.TextBox1.Text <- result3
              | true  -> this.TextBox1.Text <- "Chybný rozdíl krajních hodnot" 
              this.ProgressBar2.IsIndeterminate <- false
            } 
    Async.StartImmediate(asyncOperation())//not working with Async.Start
                                             
  • Related