如何将一个 Task<MyType> 序列转换成一个 MyType 序列?

4

我目前有一个类型为seq<System.Threading.Tasks.Task<Restaurant>>的序列,我想把它转换成类型为seq<Restaurant>的序列。

我目前正在使用TaskBuilder.fs库,从我的研究来看,我需要使用let!或do!来处理这种情况,但它们需要task {},而当与Seq.map一起使用时会带回相同的Task类型。

let joinWithReviews (r : Restaurant) =
    task {
        let! reviewResult = Reviews.Database.getByLocationId cnf.connectionString r.Restaurant_Id
        match reviewResult with
        | Ok reviewResult ->
            let restaurant = { r with Reviews = (List.ofSeq reviewResult)}
            return restaurant
        | Error ex ->
            return raise ex
    }

let indexAction (ctx : HttpContext) =
    task {
        let (cnf:Config) = Controller.getConfig ctx
        let! result = Restaurants.Database.getAll cnf.connectionString
        match result with
        | Ok result ->
            let restaurantWithReviews = (Seq.map joinWithReviews result)
            return index ctx (List.ofSeq restaurantWithReviews)
        | Error ex ->
            return raise ex
    }

我的结果类型是Seq<Restaurant>,我需要为每个餐厅添加评论,因此我使用Seq.map获取restaurantWithReviews,它的类型是seq<System.Threading.Tasks.Task<Restaurant>>,但我将无法使用它。


你是否尝试并行处理一些任务? - Tomas Petricek
我不需要并行处理,但我的数据库调用是Task类型的,而我正在使用的框架要求我的IndexAction也是这种类型。 - Danson
将一个seq<Task<TResult>>转换成seq<TResult>,最简单的方法是使用简单的|> Seq.map (fun task -> task.Result)。根据MSDN文档显示,访问任务的Result属性将会阻止线程,直到结果可用,并且请注意这将是顺序执行而不是并行处理。但这绝对是你需要做的最简单、最直接的方式。 - rmunn
谢谢!那已经足够满足我的需求了。 - Danson
如果我想要按顺序运行 Seq.map 而不必通过调用 task.Result 阻塞线程,该怎么办? - David Spiess
1个回答

3

.NET方法System.Threading.Tasks.Task.WhenAll将把seq<Task<'a>>转换为Task<'a[]>。如果你在task { }块内部,可以使用let!获取结果。

let restaurants: seq<Restaurant>

let! withReviews: Restaurant[] =
    restaurants
    |> Seq.map joinWithReviews
    |> Task.WhenAll

谢谢!那就可以了。类型有点不对,但很容易在它们之间切换。 - Danson
今天这个帮了我很多,谢谢! - Kanagawa Marcos
1
请注意,此解决方案将返回一个急切地计算出的数组。如果您想保持seq行为,FSharpPlus有一个名为SeqT.run的函数,可以在评估任务时保持类似序列的行为。即,评估前两个项目不会启动下一个项目的任务。 - Abel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接