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