Montag, 7. Dezember 2015

Akka.NET Adventskalender – Tür 7

Aktor klein ging allein...

Willkommen zu unserem zweiten Sprint. Diese Woche werden wir uns auf einzeln auftretende Aktoren konzentrieren und uns gemeinsam ansehen, was wir alles mit ihnen anstellen können.

Wenn Du vorher noch nicht viel mit Aktoren gearbeitet hast wirst Du Dir sicher ein paar Fragen stellen:
  • Wie umfangreich soll so ein Aktor werden?
  • Wie finden die Aktoren sich gegenseitig, wenn sie sich unterhalten wollen?
  • Gibt es eine maximale Anzahl von Aktoren, die Du anlegen darfst?

Wie umfangreich soll so ein Aktor werden?

Wenn Du diese Artikel liest, vermute ich, Du kommst mit einem objektorientierten Hintergrund. Dann kennst Du bestimmt das Single-Responsibility Prinzip, das Robert C. Martin zuerst unter dieser Bezeichnung in seinem Buch "Clean Code" veröffentlicht hat. Gemäß diesem Prinzip bekommt jede Klasse exakt eine Verantwortlichkeit (Original-Formulierung: "one reason to change"). Für schlechtes Design würde man Klassen halten, die zu viel Aufgaben erledigen wie z.B. ConfigurationAdminNotificationRestarterNagiosReporter.  Solch eine Klasse gibt es sicher in keinem Deiner Projekte.

Das gleiche Prinzip gilt für Aktoren. Ein Aktor erfüllt eine Aufgabe und er erfüllt sie vollständig und gut. Versuche also Deine Aktoren klein zu halten, wenn es irgend möglich ist. Wenn ein Aktor mehr erledigen muss, überlege Dir ob das nicht eine Aufgabe für einen weiteren Aktor werden könnte. Dieses Vorgehen hält Aktoren testbar, verständlich und wartbar.

Das führt uns zur zweiten Frage:

Wie finden sich die Aktoren?

Hier hast Du mehrere Möglichkeiten:
  • Wenn Du einen übergeordneten Aktor hast (im OO Umfeld kennst Du dieses Muster unter dem Namen "Mediator"), dann erzeugt dieser seine Mitstreiter und verbindet sie miteinander, indem die diversen Referenzen der einzelnen Aktoren den jeweiligen Aktoren mitgeteilt werden. So "kennt" jeder die notwendigen Aktoren.
  • Alternativ können die Kind-Aktoren auch an den übergeordneten berichten, der dann wiederum entsprechend reagiert.
  • Akka.NET kennt ein Konstrukt namens ActorSelection. Du weißt ja, dass Aktoren in einer Baumstruktur angeordnet sind. Eine Selektion ist ein Pfad (wahlweise absolut von der Wurzel aus oder relativ zur aktuellen Stelle) mit wahlweise Joker-Zeichen. Solch eine Selektion könnte wie "../worker*" oder "/user/dashboard/*" aussehen. Ersteres Beispiel würde alle mit "worker" beginnenden Eltern-Aktoren betreffen, das zweite Beispiel alle unter "user/dashoard" sitzenden Aktoren. Benutzt wird solch eine Selektion fast wie eine AktorRef, zumindest was die Möglichkeiten angeht, einen Tell() Aufruf auszulösen.
  • Es gibt einen globalen EventStream, der es ermöglicht, Ereignisse zu publizieren und zu abonnieren. Auch dazu werden wir in ein paar Tagen ein Beispiel sehen.

Etwas mehr Details zur Addressierung von Aktoren finden sich in der Akka.NET Dokumentation.

Zum demonstrieren erzeugen wir einen einfachen Aktor, der seinen Namen und den erhaltenen Text ausgibt:

using System;
using Akka.Actor;

namespace ActorSelection
{
    public class Writer : ReceiveActor
    {
        public Writer()
        {
            Receive<string>(s =>
                Console.WriteLine("{0} received {1}", Self.Path.Name, s));
        }
    }
}

Und die passende Applikation dazu:

using System;
using Akka.Actor;
using System.Threading;

namespace ActorSelection
{
    class MainClass
    {
        public static void Main(string[] args)
        {
            var system = ActorSystem.Create("Selection");

            var writer1 = system.ActorOf(Props.Create<Writer>(), "writer1");
            var writer2 = system.ActorOf(Props.Create<Writer>(), "writer2");
            var writer3 = system.ActorOf(Props.Create<Writer>(), "writer3");

            var xxx1 = system.ActorOf(Props.Create<Writer>(), "xxx1");
            var xxx2 = system.ActorOf(Props.Create<Writer>(), "xxx2");

            var printer = system.ActorOf(Props.Create<Writer>(), "printer");


            // use ActorRef
            writer2.Tell("hello");
            xxx1.Tell("Ola");

            // use ActorSelection
            system.ActorSelection("/user/writer*").Tell("hi");

            Thread.Sleep(500);
            Console.WriteLine("press [enter] to continue");
            Console.ReadLine();

            system.Shutdown();
            system.AwaitTermination();
        }
    }
}

Die Ausgabe könnte so aussehen. Die Reihenfolge kann bei jedem Aufruf anders sein.

writer1 received hi
writer2 received hello
writer2 received hi
writer3 received hi
xxx1 received Ola

Bleibt noch die dritte Frage ungeklärt.

Wieviele Aktoren darf ich erzeugen?

Die Antwort darauf ist einfach: Bis Dein Speicher voll ist. Aktoren selbst belegen reativ wenig Speicher. Die genaue Zahl kann ich aktuell leider nicht nennen, aber es kursieren immer wieder Werte von 300-400 Byte pro Aktor. Zu einem Thread werden Aktoren erst, sobald eine Nachricht empfangen wird und dieser Vorgang erfolgt kontrolliert durch das Laufzeitsystem. Also: es darf durchaus einer mehr sein, das spielt überhaupt keine Rolle, selbst tausende von Aktoren sind kein Drama.

Das war's für heute. Morgen werden wir ein kleines Spiel gemeinsam programmieren.

Keine Kommentare:

Kommentar veröffentlichen