F#列表中的Task.WaitAll

时间:2011-02-25 11:31:49

标签: list f# functional-programming parallel-extensions

我正在使用F#进行并行编程。对于固定数量的元素,例如2个元素a1,a2和函数f,我可以这样做:

let t1 = Task.Factory.StartNew(fun () -> f a1)
let t2 = Task.Factory.StartNew(fun () -> f a2)
Task.WaitAll(t1, t2)
t1.Result, t2.Result

我想知道如何用一系列元素做同样的事情:

let ts = List.map (fun a -> Task.Factory.StartNew(fun () -> f a))
Task.WaitAll(ts)
List.map (fun (t: Task<_>) -> t.Result) ts

Visual Studio发现Task.WaitAll无法接受任务&lt; T>列表作为参数。 Task.WaitAll可以将Task []作为其参数,但它没有意义,因为我需要获取Result以进行下一次计算。

3 个答案:

答案 0 :(得分:5)

正如Robert解释的那样,如果要调用WaitAll,则必须将元素序列强制转换为基类型Task,然后将其转换为数组。您可以为Task定义扩展成员,以使其更简单:

type System.Threading.Tasks.Task with
  static member WaitAll(ts) =
    Task.WaitAll [| for t in ts -> t :> Task |]

我正在使用数组理解和强制转换代替Seq.cast,因为Seq.cast采用无类型IEnumerable - 所以F#为扩展方法推断出更好的类型。

另一种选择是根本不调用WaitAll - 如果不这样做,Result属性将阻塞,直到任务完成。这意味着你无论如何都会阻塞线程(可能会有更多的阻塞,但我不确定它是否会影响性能)。如果您使用List.map收集所有结果,则行为几乎相同。

答案 1 :(得分:3)

这是一个不幸的设计。 Task.WaitAll正在使用c#params关键字来允许您指定多个参数并使它们成为方法中的数组。它还使用C#的隐式转换,以便您可以给它Task<T>。 在F#中,您必须通过显式转换并转换为数组来自己完成此操作:

let ts = [| 
     Task.Factory.StartNew(fun () -> 1)
     Task.Factory.StartNew(fun () -> 2)
     |]
Task.WaitAll(ts |> Seq.cast<Task> |> Array.ofSeq)

现在您可以从ts获得结果。

答案 2 :(得分:1)

Array也有map,所以没有理由不能将任务放在数组中。

或者你可以只为waitall转换成一个数组......