选择第一个异步结果

时间:2014-09-02 16:04:17

标签: asynchronous f# task-parallel-library sequence

是否有异步运算符来获取由两个异步值(Async< _>)首先返回的值?

例如,给定两个Async< _>一个A1在1秒后返回,A2在2秒后返回的值,然后我想要A1的结果。

原因是我想为异步序列实现一个交错函数,这样如果有两个异步序列"定义"像这样(空间表示时间与大理石图一样):

S1 = -+-----+------------+----+
S2 = ---+-------+----------+-----+

然后我想生成一个新的异步序列,其行为如下:

S3 = -+-+---+---+--------+-+--+--+

交错S1 S2 = S3

但是两个人这样做,我可能需要一种异步选择运算符来选择选择值。

我认为这就像"选择"在Go中,您可以从两个频道获取第一个可用值。

TPL有一个名为Task.WhenAny的函数 - 我可能需要类似的东西。

2 个答案:

答案 0 :(得分:4)

我不认为运营商在F#库中可用。要将此功能与现有操作结合使用,您可以使用Async.StartAsTask,然后使用现有的Task.WhenAny运算符。但是,我不确定在取消方面的表现如何。

另一种选择是使用F# Snippets web site上实施的Async.Choose运算符。这不是特别优雅,但它应该做的伎俩!为了使答案独立,代码附在下面。

/// Creates an asynchronous workflow that non-deterministically returns the 
/// result of one of the two specified workflows (the one that completes
/// first). This is similar to Task.WaitAny.
static member Choose(a, b) : Async<'T> = 
    Async.FromContinuations(fun (cont, econt, ccont) ->
      // Results from the two 
      let result1 = ref (Choice1Of3())
      let result2 = ref (Choice1Of3())
      let handled = ref false
      let lockObj = new obj()
      let synchronized f = lock lockObj f

      // Called when one of the workflows completes
      let complete () = 
        let op =
          synchronized (fun () ->
            // If we already handled result (and called continuation)
            // then ignore. Otherwise, if the computation succeeds, then
            // run the continuation and mark state as handled.
            // Only throw if both workflows failed.
            match !handled, !result1, !result2 with 
            | true, _, _ -> ignore
            | false, (Choice2Of3 value), _ 
            | false, _, (Choice2Of3 value) -> 
                handled := true
                (fun () -> cont value)
            | false, Choice3Of3 e1, Choice3Of3 e2 -> 
                handled := true; 
                (fun () -> 
                    econt (new AggregateException
                                ("Both clauses of a choice failed.", [| e1; e2 |])))
            | false, Choice1Of3 _, Choice3Of3 _ 
            | false, Choice3Of3 _, Choice1Of3 _ 
            | false, Choice1Of3 _, Choice1Of3 _ -> ignore )
        op() 

      // Run a workflow and write result (or exception to a ref cell
      let run resCell workflow = async {
        try
          let! res = workflow
          synchronized (fun () -> resCell := Choice2Of3 res)
        with e ->
          synchronized (fun () -> resCell := Choice3Of3 e)
        complete() }

      // Start both work items in thread pool
      Async.Start(run result1 a)
      Async.Start(run result2 b) )

答案 1 :(得分:3)

托马斯已经回答了准确的问题。但是,您可能有兴趣知道我的Hopac F#库直接支持Concurrent ML样式的一流,高阶,选择性事件,称为alternatives,它们直接提供{ {3}} -combinator并提供比Go的select语句更具表现力的并发抽象机制。

关于交错两个异步序列的更具体的问题,我最近开始尝试如何使用Hopac进行choose样式编程。我提出的一种可能的方法是定义一种短暂的事件流。你可以在这里找到实验代码:

如您所见,为事件流定义的操作之一是merge。您正在寻找的内容在语义上可能略有不同,但使用Hopac风格的替代方案(或并发ML风格的事件)可能很容易实现。