TcpClient.ConnectAsync or BeginConnect/EndConnect in F# async workflow - asynchronous

TcpClient.ConnectAsync is a Task, which doesn't work readily with F# async workflows.
I think I'm missing something really simple here -- is there a general way to use either the Async or Begin/End functions from workflows?

Take a look at Async.AwaitTask and Async.FromBeginEnd.
Generally this will work:
async {
...
do! client.ConnectAsync(address, port) |> Async.AwaitTask
}
However, because the Async module only works directly with Task<'T> and ConnectAsync returns Task, the code required in this particular case is slightly longer:
do! client.ConnectAsync(address, port) |> Async.AwaitIAsyncResult |> Async.Ignore
or
let! _ = client.ConnectAsync(address, port) |> Async.AwaitIAsyncResult

Related

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

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.

How to Simplify Asynchronous Programming in F#

I come from a C# background having used async/ await. I am trying to find a "less verbose" way of programming using a library.
( specifically the Microsoft Playwright library for browser automation )
let (~~) = Async.AwaitTask
let getLastPageNumber(page: IPage) =
let playwright = ~~Playwright.CreateAsync() |> Async.RunSynchronously
let browser = ~~playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions(Headless = false )) |> Async.RunSynchronously
~~page.GotoAsync("https://go.xero.com/BankRec/BankRec.aspx?accountID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&page1") |> Async.RunSynchronously |> ignore
let lastPageLink = ~~page.QuerySelectorAsync("#mainPagerEnd") |> Async.RunSynchronously
if lastPageLink = null then
//this is the last page
1
else
let lastPageNumber = ~~lastPageLink.GetAttributeAsync("href") |> Async.RunSynchronously
lastPageNumber |> int
I have shortened things a bit using the alias ~~ for Async.AwaitTask but it seems to be a lot of code to do something that was a lot easier in C#.
Async.RunSynchronously should only be used as a very last resort because it blocks a thread to perform the computation, which defeats the purpose of using async/tasks.
The F# equivalent of C#'s async/await is to use F#'s Async type, and the async computation expression. However, if you're using a .NET library which uses the .NET Task type then you can use the TaskBuilder.fs library which has a task computation expression.
Then you would write the function like this:
open FSharp.Control.Tasks
let getLastPageNumber(page: IPage) = task {
let! playwright = Playwright.CreateAsync()
let! browser = playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions(Headless = false ))
let! _ = page.GotoAsync("https://go.xero.com/BankRec/BankRec.aspx?accountID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&page1")
let! lastPageLink = page.QuerySelectorAsync("#mainPagerEnd")
if lastPageLink = null then
//this is the last page
return 1
else
let! lastPageNumber = lastPageLink.GetAttributeAsync("href")
return lastPageNumber |> int
}
Inside the computation expression (also called a builder) let! is used to await tasks.
Note that this function now returns Task<int> rather than int, so the caller would probably need to follow a similar pattern and propogate the task further.
You can read more about using async in F# here. Some of that knowledge can also be applied to the task computation expression.

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

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