2013-12-15 3 views
4

У меня есть актер, который создает еще один:Инициализация актера, прежде чем быть в состоянии справиться с некоторыми другими сообщениями

class MyActor1 extends Actor { 
    val a2 = system actorOf Props(new MyActor(123)) 
} 

Второй актер должен инициализировать (начальная загрузка) сам когда он создал, и только после этого он должен быть в состоянии выполнять другую работу.

class MyActor2(a: Int) extends Actor { 
    //initialized (bootstrapped) itself, potentially a long operation 
    //how? 
    val initValue = // get from a server 

    //handle incoming messages 
    def receive = { 
    case "job1" => // do some job but after it's initialized (bootstrapped) itself 
    } 
} 

Так что самое первое, что MyActor2 нужно сделать, это сделать какую-то работу инициализации себя. Это может занять некоторое время, потому что это запрос на сервер. Только после успешного завершения он должен иметь возможность обрабатывать входящие сообщения через receive. До этого - он не должен этого делать.

Конечно, запрос на сервер должен быть асинхронными (предпочтительно, с использованием Future, а не async, await или другой материал на высоком уровне, как AsyncHttpClient). Я знаю, как использовать будущее, но это не проблема.

Как это обеспечить?

p.s. Я предполагаю, что он должен отправить сообщение самому себе.

ответ

9

Вы можете использовать become метод для изменения поведения актера после инициализации:

class MyActor2(a: Int) extends Actor { 

    server ! GetInitializationData 

    def initialize(d: InitializationData) = ??? 

    //handle incoming messages 
    val initialized: Receive = { 
    case "job1" => // do some job but after it's initialized (bootstrapped) itself 
    } 

    def receive = { 
    case d @ InitializationData => 
     initialize(d) 
     context become initialized 
    } 
} 

Обратите внимание, что такой актер будет отбрасывать все сообщения до инициализации. Вы должны сохранять эти сообщения вручную, например, с помощью Stash:

class MyActor2(a: Int) extends Actor with Stash { 

    ... 

    def receive = { 
    case d @ InitializationData => 
     initialize(d) 
     unstashAll() 
     context become initialized 
    case _ => stash() 
    } 
} 

Если вы не хотите использовать var для инициализации можно создать инициализированное поведение, используя InitializationData так:

class MyActor2(a: Int) extends Actor { 

    server ! GetInitializationData 

    //handle incoming messages 
    def initialized(intValue: Int, strValue: String): Receive = { 
    case "job1" => // use `intValue` and `strValue` here 
    } 

    def receive = { 
    case InitializationData(intValue, strValue) => 
     context become initialized(intValue, strValue) 
    } 
} 
+0

Sorry , Я забыл упомянуть, что мне нужно инициализировать 'val initValue =' с результатом инициализации. Таким образом, в конечном итоге 'initValue' должен содержать результат процесса инициализации. Каков наилучший способ сделать это? –

+0

@Alex: см. Обновление. Вы можете изменять поведение без 'var' много раз - см. [Этот ответ] (http://stackoverflow.com/a/17208523/406435). – senia

+0

И если я хочу использовать 'var', тогда он будет иметь значение по умолчанию (null), а затем будет определено? –

4

Я не знаю, что предлагаемое решение - хорошая идея. Мне кажется неудобным отправлять сообщение инициализации. Актеры имеют жизненный цикл и предлагают несколько крючков. Когда вы посмотрите на API, вы обнаружите крюк prestart.

Поэтому я предлагаю следующее:

  • Когда актер создан, его предстартовой крючок запускается, когда вы делаете запрос на сервер, который возвращает будущее.
  • Пока будущее не завершено, все входящие сообщения спрятаны.
  • Когда будущее завершается, оно использует context.become, чтобы использовать ваш реальный/нормальный метод получения.
  • После того, как вы все разукрашиваете все.

Вот грубый набросок кода (плохое решение, см реальное решение ниже):

class MyActor2(a: Int) extends Actor with Stash{ 

    def preStart = { 
    val future = // do your necessary server request (should return a future) 
    future onSuccess { 
     context.become(normalReceive) 
     unstash() 
    } 
    } 

    def receive = initialReceive 

    def initialReceive = { 
    case _ => stash() 
    } 

    def normalReceive = { 
    // your normal Receive Logic 
    } 
} 

UPDATE: Улучшенное решение в соответствии с Senias обратной

class MyActor2(a: Int) extends Actor with Stash{ 

    def preStart = { 
    val future = // do your necessary server request (should return a future) 
    future onSuccess { 
     self ! InitializationDone 
    } 
    } 

    def receive = initialReceive 

    def initialReceive = { 
    case InitializationDone => 
     context.become(normalReceive) 
     unstash() 
    case _ => stash() 
    } 

    def normalReceive = { 
    // your normal Receive Logic 
    } 

    case class InitializationDone 
} 
+2

Извините, но это плохое решение. 'onSuccess' будет выполнен в каком-то потоке, который ничего не имеет для вашего актера. Вы получите условие гонки на 'stash()' и 'unstash()'. Никогда не разделяйте актерское состояние! Это нарушает безопасность потока. Единственным действительным способом изменения состояния актера является обработка сообщений. – senia

+0

О да, ты прав. Спасибо! Я все еще новичок в актерах и иногда забываю некоторые основные вещи :-). В улучшенном решении я отправляю сообщение в onSuccess сейчас, и делаю становление и неустойчивость в приеме. – mavilein

+0

Не могли бы вы объяснить, почему вы используете 'context.become (normalReceive)', а затем 'case _ => stash()'. Не все ли сообщения перейдут в 'normalReceive' после' стать'? Кто-нибудь из них достигнет 'case _ => stash()'? –

Смежные вопросы