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
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.