How to ignore C# awaitable Task when consumed from F#? - .net-core

I am trying to make the code below working
type Message = {
Text: string
}
[<EntryPoint>]
let main argv =
use bus = RabbitHutch.CreateBus("host=localhost")
async {
let! result = bus.PubSub.PublishAsync({Text = "text"}) |> Async.AwaitTask
}
0
Knowing that PublishAsync returns Task<unit>. I just wanted to equivalently await this method in F# method but this triggers an error:
Program.fs(14, 5): [FS0010] Unexpected symbol '}' in expression
I already checked that answer on SO, not really sure what I am supposed to do, adding |> ignore does not seem to do anything.
[EDIT]
I added an ignore:
type Message = {
Text: string
}
[<EntryPoint>]
let main argv =
use bus = RabbitHutch.CreateBus("host=localhost")
async {
let! result = bus.PubSub.PublishAsync({Text = "text"}) |> Async.AwaitTask
result |> ignore
}
0
But now I get the following warning:
Program.fs(10, 5): [FS0020] The result of this expression has type 'Async<unit>' and is implicitly ignored. Consider using 'ignore' to discard this value explicitly, e.g. 'expr |> ignore', or 'let' to bind the result to a name, e.g. 'let result = expr'.
Can't get rid of that one though, any idea?

The result of this expression:
async {
let! result = bus.PubSub.PublishAsync({Text = "text"}) |> Async.AwaitTask
result |> ignore
}
is a value of Async<unit> type. In F# and functional programming, every expression should contribute to the result of the bigger expression, otherwise it is likely a mistake. Hence your compiler reported the warning.
To tell compiler that you are okay with that, simply transform the value to the () value of the unit type (which is similar to void type in other languages) by using the ignore function.
Now look into the body of your async, in your old code, the last statement is let! result = ... and it is invalid in F# syntax. Every let or let! is the short form of let name=... in someExpression but there is no expression here except the } that’s why compiler said Unexpected symbol '}' in expression.
You fixed that by piping result to ignore. That’s okay because that is a valid expression.
Now consider your new code, you can ignore the Async<unit> to dismiss the warning, but that makes your async nonsense. You declared the async, but did not execute it. Probably you need to execute it with the Async.RunSynchronously function:
async {
let! result = bus.PubSub.PublishAsync({Text = "text"}) |> Async.AwaitTask
result |> ignore
}
|> Async.RunSynchronously
Since result is a unit value, you can clean your code with do!:
async {
do! bus.PubSub.PublishAsync({Text = "text"}) |> Async.AwaitTask
}
|> Async.RunSynchronously
Or even cleaner:
bus.PubSub.PublishAsync({Text = "text"})
|> Async.AwaitTask
|> Async.RunSynchronously

Your async computation expression returns Async<unit>. You need to actually run the computation. You can do that with Async.RunSynchronously.
async {
let! result = bus.PubSub.PublishAsync({Text = "text"}) |> Async.AwaitTask
result |> ignore
} |> Async.RunSynchronously
Though at this point, I think the computation expression is redundant. This would do the same:
bus.PubSub.PublishAsync({Text = "text"})
|> Async.AwaitTask
|> Async.RunSynchronously

Related

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.

In F#, how to always execute a bound Async process?

I have the following methods in F#:
let GetAllPatientNamesAsync() =
async {
let data = context.GetAllPatientNamesAsync()
return! Async.AwaitTask data
}
let getAllPatientNames = Async.RunSynchronously (FsNetwork.GetAllPatientNamesAsync())
This works the FIRST time:
{ m with names = getAllPatientNames}
But once getAllPatientNames has a value, calling it again does not execute the Async.RunSynchronously (FsNetwork.GetAllPatientNamesAsync()) -- rather getAllPatientNames returns its original value.
Since FsNetwork.GetAllPatientNamesAsync() is accessing the database, which is changing, after the first read getAllPatientNames is wrong.
Ofcourse, this works correctly no matter how many times I use it:
{ m with names = Async.RunSynchronously (FsNetwork.GetAllPatientNamesAsync()) }.
How should I write
let getAllPatientNames = Async.RunSynchronously (FsNetwork.GetAllPatientNamesAsync())
such that it ALWAYS executes the Async process? What am I doing wrong?
TIA
The problem is that
getAllPatientNames
is a a value and not a function. Make a it a function by changing it like below
let getAllPatientNames () = Async.RunSynchronously (FsNetwork.GetAllPatientNamesAsync())

F# async equivalent of Promise.race?

In JavaScript, there is a function called Promise.race that takes a list of promises and returns a new promise that completes when any of the input promises completes.
See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race
F# has Async.Parallel, which completes when all of the input asyncs have completed, but it does not seem to have an equivalent for any (such as Async.Race).
How can I write this in F#?
You could use tasks.
Something like this:
let race xs =
xs
|> Seq.map Async.StartAsTask
|> Task.WhenAny
|> Async.AwaitTask
Using Async.Choice from FSharp.Control:
let race xs =
async {
let! first =
xs
|> Seq.map (fun task -> async {
let! x = task
return Some x
})
|> Async.Choice
return Option.get first
}

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