Om aktörer

Det finns tre olika typer av aktörer i Akka .NET. Nedan använder jag en ReceiveActor som utmärker sig genom att den använder funktionen Receive för att ta emot ett meddelande. Exemplen som följer förutsätter att namnrymden Akka.Actor är inläst. Alla aktörer har en sökväg som är oberoende av vilken CLR-klass som beskriver dem. Givet att system är ett initierat aktörssystem med namnet MittSystem och att vi har två aktörer…

public class EnRootActor : ReceiveActor
{
}

public class EnAnnanActor : ReceiveActor
{
}

…så kommer denna kod att fallera eftersom båda aktörerna, trots sina olika klasser, gör anspråk på samma sökväg:

var a = system.ActorOf<EnRootActor>("MinRoot");
Console.WriteLine(a.Path);
var b = system.ActorOf<EnAnnanActor>("MinRoot");
Console.WriteLine(b.Path);

Jag använder Akka .NET lokalt, vilket syns på sökvägen som är akka://MittSystem/user/MinRoot vilket innebär protokollet akka, aktörssystemet MittSystem följt av sökvägen user/MinRoot. Alla användardefinierade aktörer ligger under user.

Både a och b kan samexistera givet att vi ger dem unika namn.

Aktörer behöver inte skapas på rootnivå. Om en aktör skapar en aktör organiseras den nya aktören under den som skapar aktören.

public class EnRootActor : ReceiveActor
{
   public EnRootActor()
   {
      this.Receive<Message>(m =>
      {
         var c = Context.ActorOf<EnAnnanActor>("Child");
         Console.WriteLine(c.Path);
      });
   }
}

I ovanstående fall har c sökvägen akka://MittSystem/user/MinRoot/MinChild, och eftersom vi har ett villkorslöst skapande så stöter vi på problem nästa gång vi skickar ett meddelande till EnRootActor eftersom MinChild inte kan skapas mer än en gång.

Akka .NET erbjuder möjligheten att erhålla referenser till existerande aktörer genom funktionen ActorSelection som kan agera antingen på systemet eller i kontexten för någon existerande aktör.

I detta exempel (en Console Application) är skapas en tredje aktör när aktör a hanterar meddelandet som skickas till den. Den tredje aktören får sedan ett meddelande skickat till sig från Main, vilket är en annan tråd.

class Program
{
   private static ActorSystem system;

   static void Main(string[] args)
   {
      var system = ActorSystem.Create("MittSystem");
      var a = system.ActorOf<EnRootActor>("MinRoot");
      Console.WriteLine(a.Path);
      var b = system.ActorOf<EnAnnanActor>("EnTillRoot");
      Console.WriteLine(b.Path);
      a.Tell(new Message("Helloooooo!"));

      //Godtycklig paus eftersom childaktören
      //skapas på en annan tråd.
      System.Threading.Thread.Sleep(1000);

      //Erhåll en referens till childaktören
      //och skicka ett meddelande.
      system.ActorSelection(
         "akka://MittSystem/user/MinRoot/Child").Tell(
         new Message("Snodd ref."));

      Console.ReadLine();
   }
}

public class EnRootActor : ReceiveActor
{
   public EnRootActor()
   {
      this.Receive<Message>(m =>
      {
         Console.WriteLine(m.Text);
         //Skapa en ny.
         var c = Context.ActorOf<EnAnnanActor>("Child");
         Console.WriteLine(c.Path);
      });
   }
}
   
public class EnAnnanActor : ReceiveActor
{
   public EnAnnanActor()
   {
      this.Receive<Message>(m => Console.WriteLine(m.Text));
   }
}

public class Message
{
   public string Text { get; private set; }
   public Message(string text)
   {
      this.Text = text;
   }
}

För att inte programmet ska krascha nästa gång EnRootActor tar emot meddelandet, kan man kort och gott kolla om childaktören redan är skapad.

public class EnRootActor : ReceiveActor
{
   public EnRootActor()
   {
      this.Receive<Message>(m =>
      {
         Console.WriteLine(m.Text);

         //Skapa en ny om det behövs.
         if (Context.GetChildren().Count() <= 0)
         {
            var c = Context.ActorOf<EnAnnanActor>("Child");
            Console.WriteLine(c.Path);
         }
      });
   }
}

Skulle man till äventyrs vara intresserad av vilka aktörer som är childaktörer går det bra att läsa av deras sökväg eller kanske bara deras namn:

foreach (var x in Context.GetChildren())
   Console.WriteLine(x.Path.Name);

Så här ser samma exempel ut i Visual Basic (förutsatt att du lagt på Akka .NET med NuGet):

Imports Akka.Actor

Module Module1

   Private sys As ActorSystem

   Sub Main()
      sys = ActorSystem.Create("MittSystem")
      Dim a = sys.ActorOf(Of EnRootActor)("MinRoot")
      Console.WriteLine(a.Path)
      Dim B = sys.ActorOf(Of EnAnnanActor)("EnTillRoot")
      a.Tell(New Message("Helloooooo!"))
      System.Threading.Thread.Sleep(1000)
      sys.ActorSelection( _
         "akka://MittSystem/user/MinRoot/Child").Tell( _
         New Message("Snodd ref."))
      a.Tell(New Message("Tjo!"))
      Console.ReadLine()
   End Sub

End Module

Public Class EnRootActor
   Inherits ReceiveActor

   Public Sub New()
      Me.Receive(Of Message)(AddressOf Me.ReceiveMessage)
   End Sub

   Private Sub ReceiveMessage(m As Message)
      Console.WriteLine(m.Text)
      If Context.GetChildren().Count() <= 0 Then
         Dim c = Context.ActorOf(Of EnAnnanActor)("Child")
         Console.WriteLine(c.Path)
      End If
   End Sub

End Class

Public Class EnAnnanActor
   Inherits ReceiveActor

   Public Sub New()
      Me.Receive(Of Message)(AddressOf Me.ReceiveMessage)
   End Sub

   Private Sub ReceiveMessage(m As Message)
      Console.WriteLine(m.Text)
   End Sub

End Class

Public Class Message

   Public Property Text() As String

   Public Sub New(Text As String)
      Me.Text = Text
   End Sub

End Class

Förutom ReceiveActor som alltså anropar funktionen Receive för att ta emot meddelanden, så har vi även TypedActor och UntypedActor.

En TypedActor anropar inte funktionen Receive för att ta emot meddelanden, utan har en överlagring av Handle för varje meddelandetyp som aktören kan hantera. En TypedActor ärver därför inte bara från basklassen TypedActor, utan även från det generiska interfacet IHandle<> för varje meddelandetyp som aktören ska hantera. Om man jobbar med klassen TypedActor betalar man med lite minskad flexibilitet, men vinner i ökad struktur. I detta exempel kan aktören ta emot både meddelande av typen A och B (där A och B är vanliga CLR-klasser).

class Program
{
   private static ActorSystem system;

   static void Main(string[] args)
   {
      var system = ActorSystem.Create("MittSystem");
      var actor = system.ActorOf<MyTypedActor>("MinActor");
      actor.Tell(new A());
      actor.Tell(new B());
      Console.ReadLine();
   }
}

public class MyTypedActor : TypedActor, IHandle<A>, IHandle<B>
{
   public void Handle(A message)
   {
      Console.WriteLine("Tack för A!");
   }

   public void Handle(B message)
   {
      Console.WriteLine("Tack för B!");
   }
}

public class A
{
}

public class B
{
}

En UntypedActor fungerar enligt samma princip, fast utan typer. Den har en enda funktion som blir anropad vid inkommande meddelande, och den funktionen tar emot meddelandet i object-format. Jag personligen kan inte se någon speciell poäng i att välja att ärva från UntypedActor, för om man inte har behov av typlåsningen så är ReceiveActor ett bättre val. Notera att UntypedActor skriver över funktionen OnReceive.

class Program
{
   private static ActorSystem system;

   static void Main(string[] args)
   {
      var system = ActorSystem.Create("MittSystem");
      var actor = system.ActorOf("MinActor");
      actor.Tell(new A());
      actor.Tell(new B());
      Console.ReadLine();
   }
}

public class MyUntypedActor : UntypedActor
{
   protected override void OnReceive(object message)
   {
      if (message.GetType() == typeof(A))
         Console.WriteLine("Tack för A!");
      else if (message.GetType() == typeof(B))
         Console.WriteLine("Tack för B!");
   }
}

public class A
{
}

public class B
{
}

Leave a Reply

Your email address will not be published. Required fields are marked *