F# async 尝试...终于可以了!等待任务

F# async try ... finally do! AwaitTask

问题,简而言之:

async{...} 表达式中,在 try-finally 表达式中,如何在 finally 块中使用 do! ... |> Async.AwaitTask

问题,长;失败的片段:

我在等待嵌套异步任务链时无法聚合所有异常。

我的设计意图是每个任务最终都会等待它的先行任务,我希望任务并行库使用 AggregateException 来组合所有观察到的异常。

每个async/task都可能抛出异常;以下最小示例 chains/nests 两个异步任务,每个任务抛出一个异常:

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

我的问题是,在不更改开始和等待顺序的情况下,我如何必须添加 try-catch- 或 try-finally-blocks 以便最终 Async.AwaitTask 报告这两个异常?

我正在使用 F# 版本 5。

我不是 async/task 方面的专家,但我认为这里的主要问题是 do! actionBefore |> Async.AwaitTask 永远无法执行,因此无法捕获其异常。

相反,我建议您将每个操作作为一个单独的任务开始,然后使用 Task.WhenAll 等待它们:

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

这导致 AggregateException 包含两个内部异常,这正是我认为您想要的。

请注意,在这种情况下您不需要外部 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

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

现有答案显示了关键技巧,即使用 Task.WhenAll - 与其他选项不同,它等待所有任务并聚合所有异常。补充一下,我认为您必须在此处混合任务和异步会让人感到困惑。执行此操作的普通 F# 方法是使用 Async.StartChild,但只要第一次计算失败就会失败:

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

没有 Async.WhenAll,但您可以使用 Task.WhenAll 来定义它。这是一个只有两个参数的更简单的版本:

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 }

那样的话,你可以用一种相当干净的方式写出你最初想要的东西,只需要使用 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