如何在F#中等待多个Async任务,取首个结果并取消其余?
实现F#异步任务的"先完成即返回"并取消其余任务
一、仅使用F#标准库实现
核心是通过取消令牌(CancellationToken)实现:当第一个任务完成时,触发取消信号终止其余未完成的任务,同时利用Async.Choice等待首个完成的任务。
实现代码
module Async = /// 等待第一个完成的异步任务,取消其余任务 let whenAny (asyncs: Async<'T> list) = async { use cancellationSource = new System.Threading.CancellationTokenSource() // 包装每个任务:完成后触发全局取消 let wrappedAsyncs = asyncs |> List.map (fun asyncTask -> async { let! result = asyncTask |> Async.WithCancellationToken cancellationSource.Token cancellationSource.Cancel() // 第一个完成的任务取消其余任务 return result }) // 等待第一个成功完成的任务 return! Async.Choice wrappedAsyncs }
使用示例
let fetchNumber (source : string) = async { let r = Random() let delay = r.Next(3_000) do! Async.Sleep(delay) printfn $"[{source}] 完成请求,耗时 {delay}ms" return r.Next(6) } // 调用自定义的whenAny async { let! firstResult = Async.whenAny [ fetchNumber "http://a.com" fetchNumber "http://b.com" fetchNumber "http://c.com" ] printfn $"首个返回结果: {firstResult}" } |> Async.RunSynchronously
二、借助常用库实现
可以利用.NET的Task.WhenAny结合F#异步任务的转换来快速实现,依赖F#核心库的异步转Task能力即可:
实现代码
open System.Threading.Tasks module Async = let whenAny (asyncs: Async<'T> list) = async { // 将所有异步任务转为Task let tasks = asyncs |> List.map (fun a -> Async.StartAsTask(a, cancellationToken = System.Threading.CancellationToken.None)) // 等待第一个完成的Task let! completedTask = Task.WhenAny(tasks) |> Async.AwaitTask // 取消剩余未完成的任务 tasks |> List.filter (fun t -> t <> completedTask && not t.IsCompleted) |> List.iter (fun t -> t.Cancel()) return completedTask.Result }
说明
Async.StartAsTask会将F#异步任务包装为可取消的.NET Task,Task.WhenAny原生支持等待首个完成的任务,随后我们手动取消剩余未完成的Task即可。
内容的提问来源于stack exchange,提问作者sdgfsdh
相关产品推荐
相关产品推荐

