Home > Blockchain >  F# async try ... finally do! AwaitTask
F# async try ... finally do! AwaitTask

Time:11-01

Problem, short:

In an async{...} expression, in a try-finally expression, how can I use do! ... |> Async.AwaitTask in the finally block?

Problem, long; failing snippet:

I fail to aggregate all exceptions when awaiting a chain of nested asynchronous tasks.

My design intention is that each task eventually awaits its predecessing task, and I expect the Task-Parallel-Library uses the AggregateException to combine all exceptions observed.

Each async/task may throw an exception; the following minimal example chains/nests two asynchronous tasks, each throwing an exception:

async {
    let actionBefore =
        async {
            printfn "doing sth before..."
            failwith "! fail during sth before"
        }
        |> Async.StartAsTask

    printfn "doing sth now..."
    failwith "! fail during sth now"

    do! actionBefore |> Async.AwaitTask
}
|> Async.StartAsTask
|> Async.AwaitTask
|> Async.RunSynchronously

My question is, without changing the starting and awaiting order, how do I have to add try-catch- or try-finally-blocks so that both exceptions will be reported by the final Async.AwaitTask?

I am using F# version 5.

CodePudding user response:

I'm not an expert in async/task, but I think the main problem here is that do! actionBefore |> Async.AwaitTask can never execute, so there's no way to catch its exception.

Instead, I suggest that you start each action as a separate task, and then use Task.WhenAll to await for both of them:

async {
    let actionBefore =
        async {
            printfn "doing sth before..."
            failwith "! fail during sth before"
        }
        |> Async.StartAsTask

    let actionNow =
        async {
            printfn "doing sth now..."
            failwith "! fail during sth now"
        }
        |> Async.StartAsTask

    do! Task.WhenAll(actionBefore, actionNow)
        |> Async.AwaitTask
        |> Async.Ignore
}
|> Async.StartAsTask
|> Async.AwaitTask
|> Async.RunSynchronously

This results in an AggregateException that contains two inner exceptions, which is what I think you want.

Note that you don't need the outer async in this case, so I think the following version is simpler:

let actionBefore =
    async {
        printfn "doing sth before..."
        failwith "! fail during sth before"
    }
    |> Async.StartAsTask

let actionNow =
    async {
        printfn "doing sth now..."
        failwith "! fail during sth now"
    }
    |> Async.StartAsTask

Task.WhenAll(actionBefore, actionNow)
    |> Async.AwaitTask
    |> Async.RunSynchronously
    |> ignore

CodePudding user response:

The existing answer shows the key trick, which is to use Task.WhenAll - unlike other options, this waits for all the tasks and aggregates all exceptions. Just to add to this, I think it's confusing that you have to mix tasks and asyncs here. The plain F# way of doing this is to use Async.StartChild, but this fails whenever the first computation fails:

async {
  let! t1 = Async.StartChild <| async {
    printfn "doing sth before..."
    failwith "! fail during sth before" }
  let! t2 = Async.StartChild <| async {
    printfn "doing sth now..."
    failwith "! fail during sth now" }
  let! r1 = t1
  let! r2 = t2
  return r1, r2 }
|> Async.RunSynchronously

There is no Async.WhenAll, but you can define that using Task.WhenAll. Here is a simpler version with just two arguments:

module Async = 
  let WhenBoth(a1, a2) = async {
    let r1 = a1 |> Async.StartAsTask
    let r2 = a2 |> Async.StartAsTask
    let! _ = System.Threading.Tasks.Task.WhenAll(r1, r2) |> Async.AwaitTask
    return r1.Result, r2.Result }

That way, you can write what you originally wanted in a fairly clean way, just using async:

async {
  let! t1 = Async.StartChild <| async {
    printfn "doing sth before..."
    failwith "! fail during sth before" }
  let! t2 = Async.StartChild <| async {
    printfn "doing sth now..."
    failwith "! fail during sth now" }
  return! Async.WhenBoth(t1, t2) }
|> Async.RunSynchronously
  • Related