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
andprogress
around, but those are justunit
values that do not actually do anything.Consequently, the asynchronous operation
async { operation }
that you pass toonThreadPool
does not do anything at all.In
doBusyAsync
, you useAsync.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 justoperation
.
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