Есть ли способ получить асинхронные рабочие процессы в F # для автоматического конвейера? - PullRequest
3 голосов
/ 01 августа 2011

(обратите внимание, я говорю о конвейерной обработке, как при параллельном запуске независимых процессов; не связано с оператором |>).

Так что, если у меня есть две функции

let incr x = 
  Thread.Sleep 1000
  x + 1

let product x y = 
  Thread.Sleep 1000
  x * y

Будет ли простой способ написать рабочий процесс, например, (псевдокод)

let productOfIncrements x y = 
  async {
    let! x1 = incr x
    let! y1 = incr y
    let! result = product x1 y1
    return result
  }

, который конвейеризует первые две независимые операции и, следовательно, выполняется за две секунды, или асинхронные рабочие процессы - неправильный подход к этой проблеме?Если есть хороший подход к проблеме, есть ли прямой способ расширить такой подход, скажем, для рекурсивного факториального вычисления за N + 1 секунду, а не за 2N?

Ответы [ 2 ]

6 голосов
/ 01 августа 2011

Самый простой вариант - использовать Async.StartChild.Этот примитив запускает асинхронную операцию в фоновом режиме (в пуле потоков) и возвращает «токен», который можно использовать для ожидания завершения операции.Это не блокирует рабочий процесс, поэтому вы можете продолжить выполнение других операций:

let productOfIncrements x y = 
  async {
    // Start the 'incr x' operation in background
    let! x1Op = async {return incr x} |> Async.StartChild
    // Continue doing other work 
    // (synchronously since it takes the same time as the above.)
    let y1 = incr y
    // Now wait for the result of 'incr x' (if it is still running)
    let! x1 = x1Op
    // return the product (just call the function synchronously)
    return product x1 y1
  }

Если две операции возвращают один и тот же тип, то вы также можете использовать Async.Parallel, который состоит из нескольких операций для запускапараллельно.

Если вы работаете с вычислениями, которые связаны исключительно с ЦП, и вам необходимо создать большое их количество, то вы также можете напрямую использовать .NET Tasks (см., например, thisстатья ).Задачи более эффективны, но не так элегантны в использовании (и не поддерживают красиво асинхронное ожидание).

В качестве дополнительного примечания обычно используется термин pipe в мире F # или .NET) для более сложной вещи - например, скажем, у вас есть ряд шагов, которые зависят друг от друга.При обработке нескольких входов вы можете выполнять шаги параллельно (и при этом ограничивать общий паралеллизм).Это также можно сделать с помощью асинхронных рабочих процессов F # - см. , например, эту статью .Существует также каркас с именованными конвейерами , который реализует эту концепцию.

0 голосов
/ 01 августа 2011

Хорошо, вот решение рекурсивной факториальной задачи, основанное на решении Томаса задачи ожидания. Я все еще не удовлетворен на 100%; Я чувствую, что внутренний метод должен возвращать какой-то кортеж продолжения или что-то еще; поддержание промежуточного итога кажется чем-то вроде «обмана», но в любом случае:

open System.Threading
open System

let decr x = 
  Thread.Sleep 1000
  x - 1

let product x y = 
  Thread.Sleep 1000
  x * y

let fact (n:int) :Async<int> = 
  let rec fact2 (x:int) (xMinus1Op:Async<int>) (runningProduct:Async<int>) :Async<int> =
      async {
        if x = 0 then 
          return! runningProduct
        else
          let! xMinus1 = xMinus1Op
          let! xMinus2Op = async {return decr xMinus1} |> Async.StartChild
          let! prod = runningProduct
          let! runningProduct = async {return product x prod} |> Async.StartChild
          // start both threads to execute simultaneously and feed them forward.
          return! fact2 xMinus1 xMinus2Op runningProduct
      }
  fact2 n (async{return decr n}) (async{return 1})

let start = Environment.TickCount
let result = fact 10 |> Async.RunSynchronously
printfn "%A" <| result
printfn "%A" <| Environment.TickCount - start //runs in 11 seconds, not 20.

Редактировать: может быть, проще с помощью задач:

let fact (n:int) :int = 
    let rec fact2 (x:int) (xMinus1:int) (runningProduct:int) :int =
        if x = 0 then 
            runningProduct
        else
            let xMinus2Op = new Task<int>(fun () -> decr xMinus1)
            let runningProductOp = new Task<int>(fun () -> product x runningProduct)
            xMinus2Op.Start()
            runningProductOp.Start()
            let xMinus2 = xMinus2Op.Result
            let runningProduct = runningProductOp.Result
            fact2 xMinus1 xMinus2 runningProduct
    fact2 n (decr n) (1)

Нет необходимости в рабочих процессах, просто простой императивный код; это может даже легко перевести на C #.

...