I am trying to update ProgressBar.Value in FsXaml. In C#, I used the below-mentioned code. I haven't tried to implement the C# approach in F# as using a public field (myCaller) does not seem to me as being a functional approach (let alone the fact that I do not know if it is at all possible to use this C# approach in F#).
//C# code
namespace Special_technical_dictionary_CSharp_4._011
{
//...some usings
class ExcelData
{
//...some code
public void WritingIntoDat()
{
//...some code
using (bw = new BinaryWriter(new FileStream(...some params...)))
{
while ((currrowIndex < (lastrowIndex + 1)))
{
//...some code
Form1.myCaller.updateProgressBarValue(100 * currrowIndex);
currrowIndex += 1;
}
bw.Close();
}
//...some code
}
}
}
namespace Special_technical_dictionary_CSharp_4._011
{
//...some usings
public partial class Form1 : Form
{
//...some code
public static Form1 myCaller;
public Form1()
{
InitializeComponent();
myCaller = this;
}
//...some code
public void updateProgressBarValue(int valueV)
=> progressBar.Value = (progressBar.Value == progressBar.Maximum) ? valueV : 0;
//...some code
}
}
My question is: What is the best (or at least good) functional approach in F# (FsXaml/code behind) for updating ProgressBar.Value?
EDIT1:
Irrelevant code and text deleted. Those not interested in Elmish.WPF please wait until an answer related to FsXaml appears.
EDIT2:
Elmish.WPF
I tried to deal with the ProgressBar issue using Bent Tranberg's comments & answer and his excellent example code. My adaptation works for a for-loop, but not for List.map(i)/iter(i), which are collection functions I actually need the progress bar for. Here is the simplified code:
File: MainWindow.fs
//F# code
module Elmish.MainWindow
type ProgressIndicator = Idle | InProgress of percent: int
type Model =
{
ProgressIndicatorLeft: ProgressIndicator
ProgressIndicatorRight: ProgressIndicator
}
let initialModel =
{
ProgressIndicatorLeft = Idle
ProgressIndicatorRight = Idle
}
let init() = initialModel, Cmd.none
type Msg =
| UpdateStatusLeft of progress: int
| WorkIsCompleteLeft
| UpdateStatusRight of progress: int
| WorkIsCompleteRight
| TestButtonLeftEvent
| TestButtonRightEvent
// FOR TESTING PURPOSES ONLY
let private longRunningOperationLeft dispatch = //simulating long running operation
async
{
for i in 1..100 do
do! Async.Sleep 20
dispatch (UpdateStatusLeft i) //THIS WORKS
dispatch WorkIsCompleteLeft
}
// FOR TESTING PURPOSES ONLY
let private longRunningOperationRight dispatch = //simulating long running operation
async //NOT WORKING
{
[1..10000]
|> List.mapi(fun i item ->
[1..100] |> List.reduce (*) |> ignore
dispatch(UpdateStatusRight i)
)
dispatch WorkIsCompleteRight
}
let update (msg: Msg) (m: Model) : Model * Cmd<Msg> =
match msg with
| UpdateStatusLeft progress -> { m with ProgressIndicatorLeft = InProgress progress; ProgressBackgroundLeft = Brushes.White }, Cmd.none
| WorkIsCompleteLeft -> { m with ProgressIndicatorLeft = Idle; ProgressBackgroundLeft = Brushes.LightSkyBlue }, Cmd.none
| UpdateStatusRight progress -> { m with ProgressIndicatorRight = InProgress progress; ProgressBackgroundRight = Brushes.White }, Cmd.none
| WorkIsCompleteRight -> { m with ProgressIndicatorRight = Idle; ProgressBackgroundRight = Brushes.LightSkyBlue }, Cmd.none
| TestButtonLeftEvent ->
let incrementDelayedCmd (dispatch: Msg -> unit) : unit = //THIS WORKS
let delayedDispatch = longRunningOperationLeft dispatch
Async.StartImmediate delayedDispatch
{ m with ProgressIndicatorLeft = InProgress 0 }, Cmd.ofSub incrementDelayedCmd
| TestButtonRightEvent ->
let incrementDelayedCmd (dispatch: Msg -> unit) : unit = //NOT WORKING
let delayedDispatch = longRunningOperationRight dispatch
Async.StartImmediate delayedDispatch
{ m with ProgressIndicatorRight = InProgress 0 }, Cmd.ofSub incrementDelayedCmd
let bindings(): Binding<Model,Msg> list =
[
"ProgressLeftBackg" |> Binding.oneWay(fun m -> m.ProgressBackgroundLeft)
"ProgressRightBackg" |> Binding.oneWay(fun m -> m.ProgressBackgroundRight)
"ProgressLeft" |> Binding.oneWay(fun m -> match m.ProgressIndicatorLeft with Idle -> 0.0 | InProgress v -> float v)
"ProgressRight" |> Binding.oneWay(fun m -> match m.ProgressIndicatorRight with Idle -> 0.0 | InProgress v -> float v)
"TestButtonLeft" |> Binding.cmdIf(TestButtonLeftEvent, fun m -> match m.ProgressIndicatorLeft with Idle -> true | _ -> false)
"TestButtonRight" |> Binding.cmdIf(TestButtonRightEvent, fun m -> match m.ProgressIndicatorRight with Idle -> true | _ -> false)
]
Even if binding the "i" index with the progress bar value had worked for collection functions in the MainWindow, it won't solve the problem. In a real life situation, the collection functions intended to work with the progress bar value are in other files "above" the main window file. Like this:
file: MainLogicRight.fs
//F# code
module MainLogicRight
let textBoxString3 low high path =
//some code
let myArray() =
Directory.EnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly)
|> Option.ofObj
|> optionToArraySort "..." "..."
|> Array.collect
(fun item ->
let arr =
let p = prefix + "*"
Directory.EnumerateDirectories(item, p)
|> Option.ofObj
|> optionToArraySort "..." "..."
|> Array.Parallel.mapi(fun i item ->
let arr = Directory.EnumerateFiles(item, "*.jpg", SearchOption.TopDirectoryOnly)
|> Option.ofObj
|> optionToArraySort "..." "..."
arr.Length
)
arr
)
I understand that it is (probably) not possible to bind the pb value with a non-indexed function such as Array.collect. But what is important - how to bind the pb value with the "i" index in List/Array.mapi/iteri (Array.Parallel.mapi in this case) ?
EDIT3:
Based on the last answer by Bent, the now-irrelevant texts and comments of mine were deleted.
An example based on the answers is here.
This answer explains how, in Elmish.WPF, progress updates to the user interface can be done from an async.
I have created an example on GitHub that demoes this. The example also demoes another way to call async functions and receive results. And it also demoes how to use mkProgram instead of mkSimple. The demo can be used as a starting template for your Elmish.WPF applications.
This snippet from the demo show the essential code involved in updating a user interface from an async.
Both techniques are based on code from the Elmish Book. You will find a lot of code there that is useful also in Elmish.WPF.
I haven't tried to update a progress bar here, only a status text box, but from this you'll very easily figure out what to do to update anything.
| UpdateStatusText statusText ->
{ m with StatusText = statusText }, Cmd.none
| RunWithProgress ->
let incrementDelayedCmd (dispatch: Msg -> unit) : unit =
let delayedDispatch = async {
do! Async.Sleep 1000
dispatch (UpdateStatusText "One")
do! Async.Sleep 1000
dispatch (UpdateStatusText "Two")
do! Async.Sleep 1000
dispatch (UpdateStatusText "Three")
}
Async.StartImmediate delayedDispatch
{ m with StatusText = "Started progress." }, Cmd.ofSub incrementDelayedCmd
UPDATE:
I have now updated the demo project on GitHub so that it demoes updates of a progress bar (and status text) from the async. These are snippets of the essential pieces.
Declaration of the two messages dispatched from the async.
| UpdateStatus of statusText:string * progress:int
| WorkIsComplete // This message could carry a result from the work done.
Handling of the two messages.
| UpdateStatus (statusText, progress) ->
{ m with StatusText = statusText; Progress = progress }, Cmd.none
| WorkIsComplete ->
{ m with StatusText = "Work was completed."; Progress = 0 }, Cmd.none
| RunWithProgress ->
let incrementDelayedCmd (dispatch: Msg -> unit) : unit =
let delayedDispatch = async {
do! Async.Sleep 1000
dispatch (UpdateStatus ("Early work", 30))
do! Async.Sleep 1000
dispatch (UpdateStatus ("Still working", 60))
do! Async.Sleep 1000
dispatch (UpdateStatus ("Late work", 90))
do! Async.Sleep 1000
dispatch WorkIsComplete
}
Async.StartImmediate delayedDispatch
{ m with StatusText = "Started progress." }, Cmd.ofSub incrementDelayedCmd
The field Progress is declared as an int.
Progress: int
The property Value of ProgressBar is a float, so a cast to float is needed in the binding.
"Progress" |> Binding.oneWay (fun m -> float m.Progress)
Of course we can declare Progress in the model as a float, but I wanted to take this opportunity to point out that the model doesn't have to align with the data types of the properties of the components. We can of course map in whatever way we want in the bindings.
One final note on the dispatcher. This is accessible through Cmd.ofSub, and also through WkProgram.Subscribe. More about that on another occasion maybe, but note this now: Sending messages with the dispatcher is thread safe. This means you can send progress messages (or any message) to the model also from async functions that run within your top level async function, or e.g. from a timer event, or anywhere really.
FINAL UPDATE : The demo on GitHub is now slightly more advanced than shown here, but the principle is still the same, so I won't bother to update the source in this answer. Anybody interested in this will most probably need the complete demo source anyway, unless you're already well into Elmish.WPF
The last part of the question, added later, is answered here.
When doing lengthy and/or CPU-intensive work, then this should be done as shown in the longRunningOperationLeft function below. This also shows how functions elsewhere, that should not be dependent on the GUI, can be written in such a way that progress updates can be sent to the GUI.
The longRunningOperationRight shown below is doing it the wrong way, blocking the GUI.
My expertise on async and task stuff is not very good, but I think the top-level async functions (such as longRunningOperationLeft) called from Elmish are running on the same thread as the Elmish loop, and this is why they should not be blocked with anything lengthy or CPU-intensive. Instead, that kind of blocking work needs to go into a child computation (such as workToDo). The role of longRunningOperationLeft is to await work, but not do work itself, lest it blocks the GUI.
I don't know whether List.mapi can have an async operation inside it. I suspect not. Anyhow, I suspect that won't be needed for your real-life case.
UPDATE by Mira: You are right. Not needed in my real-life case. Adding reportProgress i (like in your code) inside List/array.mapi is enough.
let private lengthyWork () =
[1..20_000_000] |> List.reduce ( * ) |> ignore
let private workToDo reportProgress = async {
reportProgress 0
lengthyWork ()
reportProgress 25
lengthyWork ()
reportProgress 50
lengthyWork ()
reportProgress 75
lengthyWork ()
reportProgress 100
return 7
}
// This is good.
let private longRunningOperationLeft dispatch = async {
let reportProgress progress = dispatch (UpdateStatusLeft progress)
let! hardWork = Async.StartChild (workToDo reportProgress)
do! Async.Sleep 1000 // Can do some async work here too, while waiting for hardWork to finish.
let! result = hardWork
dispatch WorkIsCompleteLeft
}
// This is not good. Blocking GUI.
let private longRunningOperationRight dispatch = async {
dispatch (UpdateStatusRight 0)
lengthyWork ()
dispatch (UpdateStatusRight 25)
lengthyWork ()
dispatch (UpdateStatusRight 50)
lengthyWork ()
dispatch (UpdateStatusRight 75)
lengthyWork ()
dispatch (UpdateStatusRight 100)
dispatch WorkIsCompleteRight
}
Related
I'm being a little adventurous with my code for the amount of experience I have with F# and I am a little worried about cross threading issues.
Background:
I have a number of orders where I need to validate the address. Some of the orders can be validated against google maps geocoding API which allows 50/ second. the rest are Australian PO Boxes which we don't have many of - but I need to validate them against a different API that only allows 1 call per second.
I have switched over most of my code from async{} functions to task{} functions and I am assuming to get something on several threads at the same time it needs to be in an async{} function or block and be piped to Async.Parallel
Question: Is this the right way to do this or will it fall over? I am wondering if I am fundamentally thinking about this the wrong way.
Notes:
I am passing a database context into the async function and updating the database within that function
I will call this from a C# ( WPF ) Application and report the progress
Am I going to have cross threading issues?
let validateOrder
(
order: artooProvider.dataContext.``dbo.OrdersEntity``,
httpClient: HttpClient,
ctx: artooProvider.dataContext,
isAuPoBox: bool
) =
async {
// Validate Address
let! addressExceptions = ValidateAddress.validateAddress (order, httpClient, ctx, isAuPoBox) |> Async.AwaitTask
// SaveExceptions
do! ctx.SubmitUpdatesAsync()
// return Exception count
return ""
}
let validateGMapOrders(httpClient: HttpClient, ctx: artooProvider.dataContext, orders: artooProvider.dataContext.``dbo.OrdersEntity`` list) =
async {
let ordersChunked = orders |> List.chunkBySize 50
for fiftyOrders in ordersChunked do
let! tasks =
fiftyOrders
|> List.map (fun (order) -> validateOrder (order, httpClient, ctx, false) )
|> Async.Parallel
do! Async.Sleep(2000)
}
let validateOrders (ctx: artooProvider.dataContext, progress: IProgress<DownloadProgressModel>) =
task {
let unvalidatedOrders =
query {
for orders in ctx.Dbo.Orders do
where (orders.IsValidated.IsNone)
select (orders)
}
|> Seq.toList
let auPoBoxOrders =
unvalidatedOrders
|> List.filter (fun order -> isAUPoBox(order) = true )
let gMapOrders =
unvalidatedOrders
|> List.filter (fun order -> isAUPoBox(order) = false )
let googleHttpClient = new HttpClient()
let auspostHttpclient = Auspost.AuspostApi.getApiClient ()
// Google maps validations
do! validateGMapOrders(googleHttpClient,ctx,gMapOrders)
// PO Box Validations
for position in 0 .. auPoBoxOrders.Length - 1 do
let! result = validateOrder (gMapOrders[position], auspostHttpclient, ctx, true)
do! Task.Delay(1000)
return true
}
When I have had to deal with rate-limited API problems I hide that API behind a MailboxProcessor that maintains an internal time to comply with the rate limit but appears as a normal async API from the outside.
Since you have two API's with different rate limits I'd parameterise the time delay and processing action then create one object for each API.
open System
type Request = string
type Response = string
type RateLimitedProcessor() =
// Initialise 1s in past so ready to start immediately.
let mutable lastCall = DateTime.Now - TimeSpan(0, 0, 1)
let mbox = new MailboxProcessor<Request * AsyncReplyChannel<Response>>((fun mbox ->
let rec f () =
async {
let! (req, reply) = mbox.Receive()
let msSinceCall = (DateTime.Now - lastCall).Milliseconds
// wait 1s between requests
if msSinceCall < 1000 then
do! Async.Sleep (1000 - msSinceCall)
lastCall <- DateTime.Now
reply.Reply "Response"
// Call self recursively to process the next incoming message
return! f()
}
f()
))
do mbox.Start()
member __.Process(req:Request): Async<Response> =
async {
return! mbox.PostAndAsyncReply(fun reply -> req, reply)
}
interface IDisposable with
member this.Dispose() = (mbox :> IDisposable).Dispose()
When I'm working in F# Interactive, I often want to make changes to an event handler. Simply calling the Subscribe or Add or AddHandler functions on an event causes the old event to continue being called, which is rarely the intention.
One solution is to use the IDisposable that it returns, but that requires tracking the IDisposables in your own code, which is cumbersome for exploratory tasks.
I've tried making a Dictionary<IEvent,IDisposable> to call Dispose() when the same event is subscribed to again:
let events = Dictionary<obj, IDisposable>()
let subonce (e:IEvent<'h,'e>) (handler: 'e -> unit) =
if events.ContainsKey e then
events.[e].Dispose()
events.Remove e |> ignore
let d = e.Subscribe handler
events.Add (e,d) |> ignore
let w = Window()
w.Show()
//Running this line in FSI a second time onward should Dispose() the previous subscription
subonce w.MouseUp (fun e -> printfn "%A" <| e.GetPosition(w))
Unfortunately, as it turns out, F# generates a new IEvent instance, so naively using = or obj.Equals doesn't cut it.
> w.MouseUp;;
val it : IEvent<Input.MouseButtonEventHandler,Input.MouseButtonEventArgs> =
<published event> {addHandler = <fun:it#5-70>;
createHandler = <fun:it#5-72>;
removeHandler = <fun:it#5-71>;}
> w.MouseUp;;
val it : IEvent<Input.MouseButtonEventHandler,Input.MouseButtonEventArgs> =
<published event> {addHandler = <fun:it#6-74>; //note that these functions are of a different anonymous instance
createHandler = <fun:it#6-76>;
removeHandler = <fun:it#6-75>;}
Are there any properties or fields I can find within an IEvent that would identify it against other instances of the owner and against different events in that owner?
Not exactly an answer to the question, but I can't think of many other scenarios in which you'd need to identify an event instance, so maybe this is good enough:
type OneHandler<'e> = { mutable h : 'e -> unit }
let onehandler (e:IEvent<'h,'e>) =
let h = { h = fun _ -> () }
e.Subscribe(fun e -> h.h e) |> ignore
h
let w = Window()
let wmouseup = onehandler w.MouseUp
wmouseup.h <- (fun e -> printfn "%A" <| e.GetPosition(w))
This way, by evaluating just the assignment to wmouseup.h, we can change the event handler without having to restart the w or juggle IDisposable or Handler objects.
Background.
I am trying to figure out MailboxProcessor. The idea is to use it as a some kind of state machine and pass arguments around between the states and then quit. Some parts are going to have async communication so I made a Sleep there.
It's a console application, making a Post does nothing because main thread quits and kills everything behind it. I am making a PostAndReply in main.
Also, I have tried without
let sleepWorkflow = async
, doesn't make any difference.
Questions.
(I am probably doing something wrong)
Go24 is not async. Changing RunSynchronously to StartImmediate makes no visible difference. The end should be somewhere below GetMe instead. At the same time Done is printed after Fetch. Isn't the control supposed t be returned to the main thread on sleep?
Go24, wait
go24 1, end
Fetch 1
Done
GetMe
...
Run time is terrible slow. Without delay in Fetch it's about 10s (stopwatch). I thought F# threads are lightweight and should use threadpool.
According to debugger it takes appr 1s to create every and it looks like real threads.
Also, changing to [1..100] will "pause" the program for 100s, according to ProcessExplorer 100 threads are created during that time and only then everything is printed. I would actually prefer fewer threads and slow increase.
Code.
Program.fs
[<EntryPoint>]
let main argv =
let a = Mailbox.MessageBasedCounter.DoGo24 1
let a = Mailbox.MessageBasedCounter.DoFetch 1
let b = Mailbox.MessageBasedCounter.GetMe
let task i = async {
//Mailbox.MessageBasedCounter.DoGo24 1
let a = Mailbox.MessageBasedCounter.DoFetch i
return a
}
let stopWatch = System.Diagnostics.Stopwatch.StartNew()
let x =
[1..10]
|> Seq.map task
|> Async.Parallel
|> Async.RunSynchronously
stopWatch.Stop()
printfn "%f" stopWatch.Elapsed.TotalMilliseconds
printfn "a: %A" a
printfn "b: %A" b
printfn "x: %A" x
0 // return an integer exit code
Mailbox.fs
module Mailbox
#nowarn "40"
type parserMsg =
| Go24 of int
| Done
| Fetch of int * AsyncReplyChannel<string>
| GetMe of AsyncReplyChannel<string>
type MessageBasedCounter () =
/// Create the agent
static let agent = MailboxProcessor.Start(fun inbox ->
// the message processing function
let rec messageLoop() = async{
let! msg = inbox.Receive()
match msg with
| Go24 n ->
let sleepWorkflow = async{
printfn "Go24, wait"
do! Async.Sleep 4000
MessageBasedCounter.DoDone() // POST Done.
printfn "go24 %d, end" n
return! messageLoop()
}
Async.RunSynchronously sleepWorkflow
| Fetch (i, repl) ->
let sync = async{
printfn "Fetch %d" i
do! Async.Sleep 1000
repl.Reply( "Reply Fetch " + i.ToString() ) // Reply to the caller
return! messageLoop()
}
Async.RunSynchronously sync
| GetMe (repl) ->
let sync = async{
printfn "GetMe"
repl.Reply( "GetMe" ) // Reply to the caller
return! messageLoop()
}
Async.RunSynchronously sync
| Done ->
let sync = async{
printfn "Done"
return! messageLoop()
}
Async.RunSynchronously sync
}
// start the loop
messageLoop()
)
// public interface to hide the implementation
static member DoDone () = agent.Post( Done )
static member DoGo24 (i:int) = agent.Post( Go24(i) )
static member DoFetch (i:int) = agent.PostAndReply( fun reply -> Fetch(i, reply) )
static member GetMe = agent.PostAndReply( GetMe )
I'm not necessarily sure that this is the main problem, but the nested asyncs and Async.RunSynchrously in the agent code look suspicious.
You do not need to create a nested async - you can just call asynchronous operations in the body of the match clauses directly:
// the message processing function
let rec messageLoop() = async{
let! msg = inbox.Receive()
match msg with
| Go24 n ->
printfn "Go24, wait"
do! Async.Sleep 4000
MessageBasedCounter.DoDone()
printfn "go24 %d, end" n
return! messageLoop()
| Fetch (i, repl) ->
(...)
Aside from that, it is important to understand that the agent has exactly one instance of the body computation running. So, if you block the body of the agent, all other operations will be queued.
If you want to start some task (like the synchronous operations) in the background and resume the agent immediately, you can use Async.Start inside the body (but be sure to call the main loop recursively in the main part of the body):
| Go24 n ->
// Create work item that will run in the background
let work = async {
printfn "Go24, wait"
do! Async.Sleep 4000
MessageBasedCounter.DoDone()
printfn "go24 %d, end" n }
// Queue the work in a thread pool to be processed
Async.Start(work)
// Continue the message loop, waiting for other messages
return! messageLoop()
I have problems with seemingly inconsistent behavior when cancelling different kinds of Asyncs.
To reproduce the problem, let's says there is a function that takes a list of "jobs" (Async<_> list), waits for them to complete and prints their results. The function also gets a cancellation token so it can be cancelled:
let processJobs jobs cancel =
Async.Start(async {
try
let! results = jobs |> Async.Parallel
printfn "%A" results
finally
printfn "stopped"
}, cancel)
The function is called like that:
let jobs = [job1(); job2(); job3(); job4(); job5()]
use cancel = new CancellationTokenSource()
processJobs jobs cancel.Token
And somewhat later it is cancelled:
Thread.Sleep(1000)
printfn "cancelling..."
cancel.Cancel()
When the cancellation token source is cancelled, the function should execute the finally-block and print "stopped".
That works fine for job1, 2 and 3, but doesn't work when there is a job4 or job5 in the list.
Job1 just Async.Sleeps:
let job1() = async {
do! Async.Sleep 1000000
return 10
}
Job2 starts some async childs and waits for them:
let job2() = async {
let! child1 = Async.StartChild(async {
do! Async.Sleep 1000000
return 10
})
let! child2 = Async.StartChild(async {
do! Async.Sleep 1000000
return 10
})
let! results = [child1; child2] |> Async.Parallel
return results |> Seq.sum
}
Job3 waits for some ugly wait handle that's set by some even uglier thread:
let job3() = async {
use doneevent = new ManualResetEvent(false)
let thread = Thread(fun () -> Thread.Sleep(1000000); doneevent.Set() |> ignore)
thread.Start()
do! Async.AwaitWaitHandle(doneevent :> WaitHandle) |> Async.Ignore
return 30
}
Job4 posts to and waits for a reply from a MailboxProcessor:
let job4() = async {
let worker = MailboxProcessor.Start(fun inbox -> async {
let! (msg:AsyncReplyChannel<int>) = inbox.Receive()
do! Async.Sleep 1000000
msg.Reply 40
})
return! worker.PostAndAsyncReply (fun reply -> reply) // <- cannot cancel this
}
Job5 waits for a Task (or TaskCompletionSource):
let job5() = async {
let tcs = TaskCompletionSource<int>()
Async.Start(async {
do! Async.Sleep 1000000
tcs.SetResult 50
})
return! (Async.AwaitTask tcs.Task) // <- cannot cancel this
}
Why can Job1, 2 and 3 be cancelled ("stopped" gets printed), while Job4 and 5 make the function hang "forever"?
So far I always relied on F# to handle cancellation behind the scenes - as long as I'm in async-blocks and use !s (let!, do!, return!,...) everything should be fine.. but that doesn't seem to be the case all the time.
Quote:
In F# asynchronous workflows, the CancellationToken object is passed
around automatically under the cover. This means that we don't have to
do anything special to support cancellation. When running asynchronous
workflow, we can give it cancellation token and everything will work
automatically.
Complete code is available here: http://codepad.org/euVO3xgP
EDIT
I noticed that piping an async through Async.StartAsTask followed by Async.AwaitTask makes it cancelable in all cases.
i.e. for Job4 that means changing the line:
return! worker.PostAndAsyncReply (fun reply -> reply)
to:
return! cancelable <| worker.PostAndAsyncReply (fun reply -> reply)
With cancelable being:
let cancelable (x:Async<_>) = async {
let! cancel = Async.CancellationToken
return! Async.StartAsTask(x, cancellationToken = cancel) |> Async.AwaitTask
}
The same works for making Job5 cancelable.
But.. that's just a workaround and I can hardly put that around each call to an unknown Async<_>.
Only the Async. methods handle using the default CancellationToken themselves.
In your MailboxProcessor example the cancel should go on the Start method
let! ct= Async.CancellationToken
use worker := MailboxProcessor.Start( theWork, ct)
In the TaskCompletionSource example, you are going to have to register a callback to cancel it.
let! ct = Async.CancellationToken
use canceler = ct.Register( fun () -> tcs.TrySetCanceled() |> ignore )
I am new to functional programming in general and started learning F# recently. I wanted to use an async workflow returning Async<'U option> to pick an item in a Sequence. I find a nice Seq.pick function, but I am not sure how I could use that with an async workflow.
If that is not possible, is there another alternative to using an imperative style program to pick the item from the list. The following is a modified variation of my program. Any feedback is highly appreciated.
let run = async {
while not stopped do
use! resource = acquireResourceLockAsync
let! items = fetchItemsAsync 5
let! item = Seq.pick returnIfLocked items
let! status = performTaskAsync item
do! updateStatusAsync status
do! Async.Sleep 1000
}
Thanks in anticipation.
EDIT: Updated my question based on the answer by jpalmer. I noticed both Seq.filter and Seq.pick earlier and decided that Seq.pick will meet my need better, as I need the first item that I am able to lock. However, I forgot to change the return value of my function - instead of returning true, it should return Some(item). Now with that update, is there an elegant way to approach this without 1) blocking a thread to convert Async<'U option> to 'U and 2) resorting to an imperative style looping?
I am unclear exactly what you are trying to do. If you want to convert from Async<'T> to 'T non-blocking, then you want to use let! in an async workflow. So the seq-like logic probably needs to be written as its own loop, as suggested below. If that doesn't help, then perhaps share more code, especially the intended types of items/item/returnIfLocked, as I'm unclear what's async in your example.
let asyncPick f (s:seq<_>) =
async {
use e = s.GetEnumerator()
let r = ref None
while Option.isNone(!r) && e.MoveNext() do
let! x = f e.Current
r := x
match !r with
| Some z -> return z
| None -> return failwith "no matching item found"
}
let chooser ax =
async {
let! x = ax
if x%3 = 0 then
return Some x
else
return None
}
let s = seq { for i in 1..10 do yield async { return i } }
let main() =
async {
let! firstChosen = s |> asyncPick chooser
return firstChosen
}
|> Async.RunSynchronously
|> printfn "%d"
main()
It is important to look at the signature of the function you are using,
Seq.pick expects a function which returns option<'t>, you want to use Seq.Filter which takes a function which returns a bool.
You will still have another problem though in that you have Async<bool> - you will need to convert that to a normal bool, but you could do this inside your 'Seq.Filter' function