You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Task.WhenAll遇异常永不终止,求符合预期的替代方案

F# Task.WhenAll 未按预期终止问题

我原本预期以下代码会在1秒后崩溃,但它始终没有终止:

open System
open System.Threading.Tasks

let foo () =
  task {
    do! Task.Delay(1_000_000)
    return 1
  }

let bar () =
  task {
    do! Task.Delay(1_000)
    raise (Exception "KABOOM")
    return 3
  }

let main () =
  task {
    do! Task.Delay(100)
    let! results = Task.WhenAll(foo (), bar ())
    return Seq.sum results
  }

main ()
|> fun t -> t.Result
|> printfn "%i"

1. 这一现象的原因是什么?

Task.WhenAll的设计逻辑是等待所有传入的任务完成(无论成功还是失败),只有当所有任务都结束后,才会将所有异常包装成AggregateException抛出。在你的代码里,bar()确实会在1秒后抛出异常,但foo()的延迟是1000000毫秒(约16分钟),主线程会一直等待foo()执行完毕,才会触发Task.WhenAll的异常逻辑,所以看起来程序始终没有终止。

而你提供的Async版本代码符合预期,是因为Async.Parallel的行为不同:它只要检测到任何一个异步操作失败,就会立即终止所有其他操作并抛出异常,不需要等待所有任务完成。Async代码如下:

open System

let foo () =
  async {
    do! Async.Sleep(1_000_000)
    return 1
  }

let bar () =
  async {
    do! Async.Sleep(1_000)
    raise (Exception "KABOOM")
    return 3
  }

let main () =
  async {
    do! Async.Sleep(100)
    let! results = Async.Parallel([ foo (); bar () ])
    return Seq.sum results
  }

main ()
|> Async.RunSynchronously
|> printfn "%i"

2. 是否存在符合预期的Task.WhenAll替代方案?

有几种方案可以实现“任一任务失败就立即终止并抛出异常”的效果:

方案1:结合Task.WhenAny与取消令牌

当任何一个任务完成时,检查是否失败,如果失败就取消其他未完成的任务:

open System
open System.Threading
open System.Threading.Tasks

let foo (cts: CancellationTokenSource) =
  task {
    do! Task.Delay(1_000_000, cts.Token)
    return 1
  }

let bar () =
  task {
    do! Task.Delay(1_000)
    raise (Exception "KABOOM")
    return 3
  }

let main () =
  task {
    do! Task.Delay(100)
    use cts = new CancellationTokenSource()
    let tasks = [ foo cts; bar () ]
    // 等待第一个任务完成
    let! completedTask = Task.WhenAny(tasks)
    // 检查是否失败
    if completedTask.IsFaulted then
      // 取消其他任务
      cts.Cancel()
      // 抛出异常
      return! completedTask :> Task<int[]>
    else
      // 如果第一个任务成功,等待剩余任务(可根据需求调整逻辑)
      let remainingTasks = tasks |> List.filter (fun t -> t <> completedTask)
      let! remainingResults = Task.WhenAll(remainingTasks)
      let allResults = [completedTask.Result] @ remainingResults |> Array.ofList
      return Seq.sum allResults
  }

main ()
|> fun t -> t.Result
|> printfn "%i"

方案2:自定义实现类似Async.Parallel的逻辑

手动遍历任务,监听每个任务的完成状态,一旦有任务失败就取消其他任务并抛出异常:

open System
open System.Threading
open System.Threading.Tasks

let whenAllFailFast tasks =
  task {
    use cts = new CancellationTokenSource()
    let taskCompletionSources = tasks |> List.map (fun _ -> TaskCompletionSource<int>())
    // 为每个任务注册回调
    for (task, tcs) in List.zip tasks taskCompletionSources do
      task.ContinueWith(fun t ->
        if t.IsFaulted then
          // 取消所有任务
          cts.Cancel()
          tcs.SetException(t.Exception.InnerExceptions)
        elif t.IsCanceled then
          tcs.SetCanceled()
        else
          tcs.SetResult(t.Result)
      ) |> ignore
    // 等待所有TCS完成(只要有一个失败就会触发终止)
    let! results = Task.WhenAll(taskCompletionSources |> List.map (fun tcs -> tcs.Task))
    return results
  }

let foo () =
  task {
    do! Task.Delay(1_000_000)
    return 1
  }

let bar () =
  task {
    do! Task.Delay(1_000)
    raise (Exception "KABOOM")
    return 3
  }

let main () =
  task {
    do! Task.Delay(100)
    let! results = whenAllFailFast [foo (); bar ()]
    return Seq.sum results
  }

main ()
|> fun t -> t.Result
|> printfn "%i"

这两种方案都能实现“任一任务失败就立即终止”的效果,符合你最初的预期。

内容的提问来源于stack exchange,提问作者sdgfsdh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 11:13:18