10

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?

2
  • Why is processEscalation awaiting the task only to ignore it? Commented Dec 30, 2016 at 11:35
  • processEscalation is used to make a webservice request but it returns a Task<T>. For this particular script I am only calling it for side effects, not interested in the returned value. Sorry I am relatively new to functional programming so I recognise that this may not be the best way. Commented Dec 30, 2016 at 11:40

1 Answer 1

9

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

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks that worked! Does this method block threads? Can you suggest a pattern for doing this more elegantly? Would using an agent (mailboxProcessor) be more appropriate?
RunSynchronously executes an async block on the current thread, effectively blocking it in your case (since you want to wait for task completion).
Mailbox processor would be a conceptual overkill, but what I wanted to suggest would operate on a similar principle - a primitive that recursively loops over a sequence of asyncs and collects the results, much like a mailbox processor's loop would recursively call itself.
Thanks it works great. Final question, in an async workflow, what's the difference between return! and return? The MSDN article doesn't even mention return!. learn.microsoft.com/en-us/dotnet/articles/fsharp/… if return was used in the workflow it would result in async<Task<'T>> without unwrapping the task?
It's roughly the same as let! + return.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.