Running Parallel async functions and waiting for the result in F# in a loop - asynchronous

I have a function that I want to run 5 at a time from a chunked list and then wait a second so as not to hit an Api rate limit. I know the code is wrong but it is as far as I have got.
let ordersChunked = mOrders |> List.chunkBySize 5
for fiveOrders in ordersChunked do
let! tasks =
fiveOrders
|> List.map (fun (order) -> trackAndShip(order) )
|> Async.Parallel
Async.Sleep(1000) |> ignore
trackAndShip is an async function not a task and I get a little confused about the two.
Some of the answers I have read add |> Async.RunSynchronously to the end of it - and I do that at the top level but I don't feel it is good practice here ( and I would need to convert async to Task? )
This is probably a very simple answer if you are familiar with async / tasks but I am still "finding my feet" in this area.

Unlike C#'s tasks, Async computation do not start when they are created and you have to explicitly start them. Furthermore, even if you would start the Async operation, it won't do what you expect because the sleep will not execute because you just ignore it.
From the let! in your example code, I'm going to assume you're the code snippet is inside a computation expression. So the following may be what you want
let asyncComputation =
async {
let ordersChunked = [] |> List.chunkBySize 5
for fiveOrders in ordersChunked do
let! tasks =
fiveOrders
|> List.map (fun (order) -> trackAndShip(order) )
|> Async.Parallel
do! Async.Sleep(1000) // Note the do! here, without it your sleep will be ignored
}
Async.RunSynchronously asyncComputation
Note that the Async.Sleep is now chained into the CE by using do!. The Async.RunSynchronously call is one way to start the async computation: it runs the async operation and blocks the current thread while waiting for the async operation's result. There are many other ways of running it described in the documentation.

Related

Why do I have to wrap an Async<T> into another async workflow and let! it?

I'm trying to understand async workflows in F# but I found one part that I really don't understand.
The following code works fine:
let asynWorkflow = async{
let! result = Stream.TryOpenAsync(partition) |> Async.AwaitTask
return result
}
let stream = Async.RunSynchronously asynWorkflow
|> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition)
I define a async workflow where TryOpenAsync returns a Task<StreamOpenResult> type. I convert it to Async<StreamOpenResult> with Async.AwaitTask. (Side quest: "Await"Task? It doesn't await it just convert it, does it? I think it has nothing to do with Task.Wait or the await keyword). I "await" it with let! and return it.
To start the workflow I use RunSynchronously which should start the workflow and return the result (bind it). On the result I check if the Stream is Found or not.
But now to my first question. Why do I have to wrap the TryOpenAsync call in another async computation and let! ("await") it?
E.g. the following code does not work:
let asynWorkflow = Stream.TryOpenAsync(partition) |> Async.AwaitTask
let stream = Async.RunSynchronously asynWorkflow
|> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition)
I thought the AwaitTask makes it an Async<T> and RunSynchronously should start it. Then use the result. What do I miss?
My second question is why is there any "Async.Let!" function available? Maybe because it does not work or better why doesn't it work with the following code?
let ``let!`` task = async{
let! result = task |> Async.AwaitTask
return result
}
let stream = Async.RunSynchronously ( ``let!`` (Stream.TryOpenAsync(partition)) )
|> fun openResult -> if openResult.Found then openResult.Stream else Stream(partition)
I just insert the TryOpenAsync as a parameter but it does not work. By saying does not work I mean the whole FSI will hang. So it has something to do with my async/"await".
--- Update:
Result of working code in FSI:
>
Real: 00:00:00.051, CPU: 00:00:00.031, GC gen0: 0, gen1: 0, gen2: 0
val asynWorkflow : Async<StreamOpenResult>
val stream : Stream
Result of not working code in FSI:
>
And you cannot execute anything in the FSI anymore
--- Update 2
I'm using Streamstone. Here the C# example: https://github.com/yevhen/Streamstone/blob/master/Source/Example/Scenarios/S04_Write_to_stream.cs
and here the Stream.TryOpenAsync: https://github.com/yevhen/Streamstone/blob/master/Source/Streamstone/Stream.Api.cs#L192
I can't tell you why the second example doesn't work without knowing what Stream and partition are and how they work.
However, I want to take this opportunity to point out that the two examples are not strictly equivalent.
F# async is kind of like a "recipe" for what to do. When you write async { ... }, the resulting computation is just sitting there, not actually doing anything. It's more like declaring a function than like issuing a command. Only when you "start" it by calling something like Async.RunSynchronously or Async.Start does it actually run. A corollary is that you can start the same async workflow multiple times, and it's going to be a new workflow every time. Very similar to how IEnumerable works.
C# Task, on the other hand, is more like a "reference" to an async computation that is already running. The computation starts as soon as you call Stream.TryOpenAsync(partition), and it's impossible to obtain a Task instance before the task actually starts. You can await the resulting Task multiple times, but each await will not result in a fresh attempt to open a stream. Only the first await will actually wait for the task's completion, and every subsequent one will just return you the same remembered result.
In the async/reactive lingo, F# async is what you call "cold", while C# Task is referred to as "hot".
The second code block looks like it should work to me. It does run it if I provide dummy implementations for Stream and StreamOpenResult.
You should avoid using Async.RunSynchronously wherever possible because it defeats the purpose of async. Put all of this code within a larger async block and then you will have access to the StreamOpenResult:
async {
let! openResult = Stream.TryOpenAsync(partition) |> Async.AwaitTask
let stream = if openResult.Found then openResult.Stream else Stream(partition)
return () // now do something with the stream
}
You may need to put a Async.Start or Async.RunSynchronously at the very outer edge of your program to actually run it, but it's better if you have the async (or convert it to a Task) and pass it to some other code (e.g. a web framework) that can call it in a non-blocking manner.
Not that I want to answer your question with another question, but: why are you doing code like this anyway? That might help to understand it. Why not just:
let asyncWorkflow = async {
let! result = Stream.TryOpenAsync(partition) |> Async.AwaitTask
if result.Found then return openResult.Stream else return Stream(partition) }
There's little point in creating an async workflow only to immediately call RunSynchronously on it - it's similar to calling .Result on a Task - it just blocks the current thread until the workflow returns.

f# perform await async methods in list.iteri

I have the following code in F# 4.0
let processEscalation escalationAction (escalationEvents:UpdateCmd.Record list) =
printf "%A" Environment.NewLine
printf "Started %A" escalationAction
escalationEvents
|> List.iter ( fun x ->
printf "%A" Environment.NewLine
printf "escalation %A for with action: %A" x.incident_id escalationAction
service.PostAction(new Models.Action(x.incident_id, escalationAction, "escalated"))
|> Async.AwaitTask
|> ignore)
let ComposeEscalation() =
let escalationlevels = ["ESC1 REACHED"; "ESC2 REACHED"; "ESC3 REACHED"]
escalationlevels
|> List.map getEscalationEvents
|> List.iteri (fun i x -> processEscalation escalationlevels.[i] x)
where the following line is a call to a C# async method that that returns Task
service.PostAction(new Models.Action(x.incident_id, escalationAction, "escalated"))
The compose escalation method calls the processEscalation three times.
However, the second call starts before the first call is complete.
How can I make sure that the the last line, list.iteri awaits and processes them sequentially?
Perhaps the processEscalation should be in an async computation expression?
What Async.AwaitTask does is that it returns an Async computation that can be used to wait for the task to complete. In your case, you never do anything with it, so the loop just proceeds to the next iteration.
You want something like this:
service.PostAction(new Models.Action(x.incident_id, escalationAction, "escalated"))
|> Async.AwaitTask
|> Async.RunSynchronously
|> ignore
This should have the effect you expect, though certainly there are nicer, more composable ways of expressing such logic.
Edit: What I meant was something like this, a counterpart to the core Async.Parallel function:
module Async =
let sequential (asyncs: seq<Async<'t>>) : Async<'t []> =
let rec loop acc remaining =
async {
match remaining with
| [] -> return Array.ofList (List.rev acc)
| x::xs ->
let! res = x
return! loop (res::acc) xs
}
loop [] (List.ofSeq asyncs)
Then you can do something along these lines:
escalationEvents
// a collection of asyncs - note that the task won't start until the async is ran
|> List.map (fun x ->
async {
let task =
service.PostAction(new Models.Action(x.incident_id, escalationAction, "escalated"))
return! Async.AwaitTask task
})
// go from a collection of asyncs into an async of a collection
|> Async.sequential
// you don't care about the result, so ignore it
|> Async.Ignore
// now that you have your async, you need to run it in a way that makes sense
// in your context - Async.Start could be another option.
|> Async.RunSynchronously
The upside here is that instead of bundling everything into a single loop, you've split the computation into well-delimited stages. It's easy to follow and refactor (e.g. if you need to process those events in parallel instead, you just switch out one step in the pipeline).

bound/throttle concurrent jobs, without creating a thread-per-job

I want to process a collection of io-bound jobs concurrently, but bound/limit the number of outstanding (actively running) concurrent jobs.
Chunking is an easy way to increase concurrency, but creates bottlenecks if the items take varying amounts of time.
The way I found to do this is has some issues 1). Is there a way do this avoiding the issues below while remaining comparably idiomatic and succinct?
1) use a BlockingCollection (shown below). However, this leads to a solution in which the concurrency here is generated by boundedSize number of "consumer" threads. I'm looking a solution that doesn't require boundedSize number of threads to achieve boundedSize concurrent jobs. (what if boundedSize is very large?). I didn't see how I could take an item, process it, and then signal completion. I can only take items... and since I don't want to rip through the whole list at once, the consumer needs to run it's work Synchronously.
type JobNum = int
let RunConcurrentlyBounded (boundedSize:int) (start : JobNum) (finish : JobNum) (mkJob: JobNum -> Async<Unit>) =
// create a BlockingCollection
use bc = new BlockingCollection<Async<Unit>>(boundedSize)
// put async jobs on BlockingCollection
Async.Start(async {
{ start .. finish }
|> Seq.map mkJob
|> Seq.iter bc.Add
bc.CompleteAdding()
})
// each consumer runs it's job synchronously
let mkConsumer (consumerId:int) = async { for job in bc.GetConsumingEnumerable() do do! job }
// create `boundedSize` number of consumers in parallel
{ 1 .. boundedSize }
|> Seq.map mkConsumer
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
let Test () =
let boundedSize = 15
let start = 1
let finish = 50
let mkJob = (fun jobNum -> async {
printfn "%A STARTED" jobNum
do! Async.Sleep(5000)
printfn "%A COMPLETED" jobNum
})
RunConcurrentlyBounded boundedSize start finish mkJob
I'm aware of TPL and mailbox processors, but thought there might've been something simple & robust, but avoids the high number of thread creation route.
Ideally there would just be one producer thread and one consumer thread; I suspect that BlockingCollection might not be the right concurrency primitive for such a case?
this seems as good as I'm going to get, by using SemaphoreSlim.
I suppose the underlying ThreadPool is really controlling the concurrency here.
let RunConcurrentlySemaphore (boundedSize:int) (start : JobNum) (finish : JobNum) (mkJob: JobNum -> Async<Unit>) =
use ss = new SemaphoreSlim(boundedSize);
{ start .. finish }
|> Seq.map (mkJob >> fun job -> async {
do! Async.AwaitTask(ss.WaitAsync())
try do! job finally ss.Release() |> ignore
})
|> Async.Parallel
|> Async.RunSynchronously

How can I throttle a large array of async workflows passed to Async.Parallel

I have an array holding a large number of small async database queries; for example:
// I actually have a more complex function that
// accepts name/value pairs for query parameters.
let runSql connString sql = async {
use connection = new SqlConnection(connString)
use command = new SqlCommand(sql, connection)
do! connection.OpenAsync() |> Async.AwaitIAsyncResult |> Async.Ignore
return! command.ExecuteScalarAsync() |> Async.AwaitTask
}
let getName (id:Guid) = async {
// I actually use a parameterized query
let querySql = "SELECT Name FROM Entities WHERE ID = '" + id.ToString() + "'"
return! runSql connectionString querySql
}
let ids : Guid array = getSixtyThousandIds()
let asyncWorkflows = ids |> Array.map getName
//...
Now, the problem: The next expression runs all 60K workflows at once, flooding the server. This leads to many of the SqlCommands timing out; it also typically causes out of memory exceptions in the client (which is F# interactive) for reasons I do not understand and (not needing to understand them) have not investigated:
//...
let names =
asyncWorkflows
|> Async.Parallel
|> Async.RunSynchronously
I've written a rough-and-ready function to batch the requests:
let batch batchSize asyncs = async {
let batches = asyncs
|> Seq.mapi (fun i a -> i, a)
|> Seq.groupBy (fst >> fun n -> n / batchSize)
|> Seq.map (snd >> Seq.map snd)
|> Seq.map Async.Parallel
let results = ref []
for batch in batches do
let! result = batch
results := (result :: !results)
return (!results |> List.rev |> Seq.collect id |> Array.ofSeq)
}
To use this function, I replace Async.Parallel with batch 20 (or another integer value):
let names =
asyncWorkflows
|> batch 20
|> Async.RunSynchronously
This works reasonably well, but I would prefer to have a system that starts each new async as soon as one completes, so rather than successive batches of size N starting after each previous batch of size N has finished, I am always awaiting N active SqlCommands (until I get to the end, of course).
Questions:
Am I reinventing the wheel? In other words, are there library functions that do this already? (Would it be profitable to look into exploiting ParallelEnumerable.WithDegreeOfParallelism somehow?)
If not, how should I implement a continuous queue instead of a series of discrete batches?
I am not primarily seeking suggestions to improve the existing code, but such suggestions will nonetheless be received with interest and gratitude.
FSharpx.Control offers an Async.ParallelWithThrottle function. I'm not sure if it is the best implementation as it uses SemaphoreSlim. But the ease of use is great and since my application doesn't need top performance it works well enough for me. Although since it is a library if someone knows how to make it better it is always a nice thing to make libraries top performers out of the box so the rest of us can just use the code that works and just get our work done!
Async.Parallel had support for throttling added in FSharp v 4.7. You do:
let! results = Async.Parallel(workflows, maxDegreeOfParallelism = dop)
if doing more than 1200 workflows concurrently in FSharp.Core versions <= 6.0.5, see this resolved issue
Proposal for a more explicit API

Async workflows in F# - Execute in parallel and do not wait for the result

Is it possible to use Async workflows in F#, execute multiple async operations in parallel but do not wait for the result ? Does it makes sense ? :)
So instead of this :
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> Async.RunSynchronously
|> ignore
Do something like this :
let runAll() =
urlList
|> Seq.map fetchAsync
|> Async.Parallel
|> ignore
Of course I need to start the the operation, so I tried just starting the execution of the async operation inside the fetchAsync function, it then works just by calling the function :
let runAll() =
urlList
|> Seq.map fetchAsync
|> ignore
But then, my code is no longer parallelized :/
I may have missed something obvious.
Thanks for the help!
My understanding is you want to start up a whole stack of Async operations but then do something else without waiting for them to complete. In this case, you want to use Async.Start like this:
let runAll() =
urlList
|> List.map Async.Start

Resources