22.3. Aktorzy — przykład 2

W pliku Guess2.scala znajduje się inna implementacja przykładu. Poniższy listing przedstawia jego pierwszą część.

Plik Guess2.scala:
import akka.actor._
object Guess2 extends App {
  println("Guess2: begin")
  val system = ActorSystem("Guess2")
  val server = system.actorOf(Props[GuessServer2],"GuessServer2") 
  val client = system.actorOf(Props[GuessClient2]) 
  import scala.concurrent.duration._ 
  implicit val timeout = akka.util.Timeout(5.seconds) 
  import system.dispatcher 
  import akka.pattern.ask 
  val responseFuture = client ? 'start 
  responseFuture.onSuccess { case response => 
    println("Guess2: received response: "+response)
    client ! 'stop
  }
  println("Guess2: end") 
}

Implementacja obiektu Guess2 różni się od implementacji obiektu Guess1 przedstawionego w poprzednim podrozdziale. W wierszu tworzymy instancję servera nadając mu nazwę „GuessServer2”. Nazwę tego aktora wykorzystamy w kodzie klienta, aby uzyskać adres serwera. W wierszu tworzymy klienta. W tym przykładzie klasa klienta nie ma parametru. W wierszu przesyłamy do klienta komunikat 'start, jednak tym razem wykorzystujemy do tego metodę ?. Metoda ? przesyła do obiorcy komunikat oraz zwraca rezultat reprezentujący odpowiedź, jaką ten odbiorca powinien zwrócić nadawcy. Metoda ?, podobnie jak !, działa asynchronicznie. Nie blokuje działania wątku do momentu otrzymania odpowiedzi — zamiast tego zwraca rezultat w postaci instancji klasy Future. Ta instancja reprezentuje wartość, która zostanie obliczona (w naszym przypadku ta wartość nadejdzie jako odpowiedź od klienta) w przyszłości. Aby możliwe było użycie metody ?, w poprzedzających ją wierszach dokonywane są pewne przygotowania. W wierszu tworzona jest niejawnie przekazywana wartość typu Timeout, określająca maksymalny czas oczekiwania na odpowiedź. Wykorzystanie wyrażenia 5.seconds, określającego czas oczekiwania jako 5 sekund, jest możliwe dzięki instrukcji import z wiersza . W wierszach i znajdują się kolejne instrukcje importu potrzebne, aby możliwe było użycie metody ?. Wywołana w wierszu metoda onSuccess otrzymuje jako argument funkcję częściową określającą, jakie działania mają zostać wykonane, kiedy wartość responseFuture zostanie obliczona (czy raczej otrzymana w tym przypadku). Działania te polegają na wypisaniu na ekranie komunikatu oraz wysłaniu klientowi komunikatu 'stop. W wierszu na ekranie wypisywany jest komunikat. Jak zobaczymy dalej, ten komunikat zostanie wyświetlony w początkowym okresie działania programu.

Klient jest implementowany przez klasę GuessClient2, której definicja jest pokazana poniżej.

Plik Guess2.scala:
class GuessClient2 extends Actor {
  println("GuessClient2: starting")
  private[this] var n: Int = 5
  val server = context.actorSelection("../GuessServer2") 
  def receive = {
    case 'start => 
      val guessSender = sender 
      send(n) 
      context.become { 
        case 'correct => 
          println(s"GuessClient2: solution found: $n")
          context.unbecome() 
          guessSender ! n 
        case 'tooSmall => 
          println("GuessClient2: too small")
          n = n + 1
          send(n)
        case 'tooBig => 
          println("GuessClient2: too big")
          n = n - 1
          send(n)
      }
    case 'stop => 
      println("GuessClient2: stopping")
      context.system.shutdown()
  }
  def send(msg: Int) {
    println(s"GuessClient2: sending $msg")
    server ! msg
  }
}

Klasa klienta nie ma parametrów, a adres serwera uzyskuje za pomocą metody actorSelection z wiersza . Metodzie tej przekazujemy łańcuch znakowy określający ścieżkę szukanego aktora. W naszym przypadku ścieżka zawiera nazwę aktora przypisaną mu wcześniej w wierszu , poprzedzoną łańcuchem „../”, który wskazuje, że jest to scieżka względna, a szukany aktor znajduje się bezpośrednio „pod” rodzicem bieżącego aktora. Metoda receive definiuje obsługę komunikatów 'start (wiersz ) oraz 'stop (wiersz ). Po otrzymaniu komunikatu 'start, w wierszu zapamiętywana jest referencja do nadawcy tego komunikatu. Następnie wysyłamy do serwera pierwszy komunikat (wiersz ). W wierszu wywoływana jest metoda become, której przekazujemy funkcję częściową implementującą obsługę otrzymywanych komunikatów. Wywołanie tej metody powoduje zastąpienie bieżącej procedury obsługi komunikatów (która jest zdefiniowane przez metodę receive i zawiera obsługę komunikatów 'start i 'stop) nową procedurą obsługi. Ta nowa procedura obsługuje komunikaty 'correct (wiersz ), 'tooSmall (wiersz ) i 'tooBig (wiersz ). Procedura obsługi komunikatu 'correct zawiera wywołanie metody unbecome (wiersz ), która przywraca poprzednią procedurę obsługi, a więc po jej wykonaniu aktor znowu obsługuje komunikaty 'start i 'stop, natomiast nie obsługuje już komunikatów 'correct, 'tooSmall oraz 'tooBig. W wierszu przesyłamy odpowiedź do nadawcy komunikatu 'start, co w rezultacie powoduje wywołanie funkcji przekazanej metodzie onSuccess w wierszu .

Klasa GuessServer2 jest implementacją aktora grającego rolę serwera.

Plik Guess2.scala:
class GuessServer2 extends Actor {
  val n = scala.util.Random.nextInt(10)
  println("GuessServer2: starting")
  def receive = {
    case x:Int if x > n => send(sender, 'tooBig)
    case x:Int if x < n => send(sender, 'tooSmall)
    case x:Int =>          send(sender, 'correct)
  }
  def send(sender: ActorRef, msg: Symbol) {
    println(s"GuessServer2: sending $msg")
    sender ! msg
  }
}

Poniżej przedstawiony jest przykładowy rezultat wykonania programu.

$ scala Guess2
Guess2: begin
GuessClient2: starting
GuessServer2: starting
Guess2: end
GuessClient2: sending 5
GuessServer2: sending 'tooBig
GuessClient2: too big
GuessClient2: sending 4
GuessServer2: sending 'tooBig
GuessClient2: too big
GuessClient2: sending 3
GuessServer2: sending 'tooBig
GuessClient2: too big
GuessClient2: sending 2
GuessServer2: sending 'correct
GuessClient2: solution found: 2
Guess2: received response: 2
GuessClient2: stopping

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.