Как избежать изменяемой ссылки на экземпляр Actor

Я пытаюсь получить некоторый опыт с актерами Akka.NET в F#. У меня есть сценарий, когда один актер должен раскрутить другого актера как своего ребенка. Первый актер конвертирует каждое сообщение, а затем отправляет результат другому актеру. я использую actorOf2 функция для появления актеров. Вот мой код:

let actor1 work1 work2 =
  let mutable actor2Ref = null
  let imp (mailbox : Actor<'a>) msg =
    let result = work1 msg
    if actor2Ref = null then 
      actor2Ref <- spawn mailbox.Context "decide-actor" (actorOf2 <| work2)
    actor2Ref <! result
  imp

let actor1Ref = actor1 work1' work2'
  |> actorOf2 
  |> spawn system "my-actor"

Что мне не нравится, так это изменчивая ссылка на актера. Но я должен был сделать это изменчивым, потому что мне нужно mailbox.Context порождать дочернего актера, и у меня нет контекста до первого вызова. Я видел этот вопрос, но я не знаю, как применить его к моей функции.

В более сложном сценарии мне нужна коллекция дочерних акторов, разделенная ключом. Я использую словарь актерских ссылок в этом сценарии. Есть ли лучший (более F#-ish) способ?

2 ответа

Решение

Чтобы сохранить ваше "состояние" между итерациями, вам нужно сделать итерации явными. Таким образом, вы можете передать текущее состояние в качестве аргумента хвостового вызова. Так же, как в вопросе, который вы связали:

let actor1 work1 work2 (mailbox : Actor<'a>) =
  let rec imp actor2 =
    actor {
      let! msg = mailbox.Receive()
      let result = work1 msg

      let actor2 =
        match actor2 with
        | Some a -> a // Already spawned on a previous iteration
        | None -> spawn mailbox.Context "decide-actor" (actorOf2 <| work2)

      actor2 <! result
      return! imp (Some actor2)
    }

  imp None

И теперь вам не нужно использовать actorOf2 или же actorOf за порождение этого актера, потому что он уже имеет правильную подпись:

let actor1Ref = 
  actor1 work1' work2'
  |> spawn system "my-actor"

,
РЕДАКТИРОВАТЬ
Если вы беспокоитесь о дополнительном шаблоне, ничто не мешает вам упаковать шаблон как функцию (в конце концов, actorOf2 делает что-то подобное):

let actorOfWithState (f: Actor<'msg> -> 'state -> 'msg -> 'state) (initialState: 'state) mailbox =
  let rec imp state =
    actor {
      let! msg = mailbox.Receive()
      let newState = f mailbox state msg
      return! imp newState
    }

  imp initialState

А потом:

let actor1 work1 work2 (mailbox : Actor<'a>) actor2 msg =
  let result = work1 msg
  let actor2 =
    match actor2 with 
    | Some a -> a
    | None -> spawn mailbox.Context "decide-actor" (actorOf2 work2)

  actor2 <! result
  actor2

let actor1Ref = 
  actor1 work1' work2'
  |> actorOfWithState
  |> spawn system "my-actor"

Вы можете сделать что-то в этом духе и просто не хранить ссылку на дочерний актер, потому что Контекст уже делает это за вас.

let actor =
    let ar = mailbox.Context.Child(actorName)
    if ar.IsNobody() then
        spawn mailbox.Context actorName handler
    else ar

Если поиск в Context.Child окажется слишком медленным, создать запомненную функцию, скрывающую изменчивость от другого кода, будет довольно просто.

Другие вопросы по тегам