Как избежать изменяемой ссылки на экземпляр 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 окажется слишком медленным, создать запомненную функцию, скрывающую изменчивость от другого кода, будет довольно просто.