Tag: Akka .NET

  • Om aktörer

    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
    {
    }
  • Schemalagda meddelanden i Akka .NET

    Schemalagda meddelanden i Akka .NET

    Denna gång tänker jag använda en Windows Forms Application för att visa schemaläggaren i Akka .NET. Som vanligt, för att komma åt Akka .NET i ditt projekt, växla till fönstret Package Manager Console och skriv Install-Package Akka. Om du inte ser Package Manager Console, klicka på rullgardinsmenyn Tools, välj Library Package Manager och Package Manager Console.

    Programmet består av följande meddelandeklass:

    public class TimedMessage
    {
       public string Message { get; private set; }
       public TimedMessage(string message)
       {
          this.Message = message;
       }
    }

    …och följande aktör:

    public class Listen : ReceiveActor
    {
       public Listen()
       {
          this.Receive<TimedMessage>(x =>
          {
             System.Diagnostics.Debug.WriteLine(x.Message);
          });
       }
    }

    Huvudfönstret har läst in namnrymden Akka.Actor för att några extension methods ska vara tillgängliga…

    using Akka.Actor;

    …och innehåller en knapp som heter button1. I formulärets Load-event skapas aktörssystemet och aktören och under knappen så skickar jag ett meddelande (TimedMessage) till aktören.

    public partial class Form1 : Form
    {
       private ActorSystem actorSystem;
       private IActorRef listen;
    
       public Form1()
       {
          InitializeComponent();
       }
    
       private void button1_Click(object sender, EventArgs e)
       {
          this.listen.Tell(new TimedMessage("Helloooo!"));
       }
    
       private void Form1_Load(object sender, EventArgs e)
       {
          this.actorSystem = ActorSystem.Create("ActorSystem");
          this.listen = this.actorSystem.ActorOf(
             Props.Create(typeof(Listen)));
       }
    }

    Om du testar detta lilla exempel så märker du att meddelandet går iväg till aktören i samma ögonblick som du klickar på knappen. Men istället för att anropa aktörens Tell-funktion i Click-eventet, så kan vi använda aktörssystemets Scheduler-property. Den har ett antal funktioner för schemaläggning och upprepning. Följande förändring gör att meddelandet går iväg först tre sekunder efter att man klickar på knappen.

    private void button1_Click(object sender, EventArgs e)
    {
       this.actorSystem.Scheduler.ScheduleTellOnce(
          TimeSpan.FromSeconds(3), this.listen,
          new TimedMessage("Helloooo!"), ActorRefs.Nobody);
    }

    Funktionen ScheduleTellOnce schemalägger ett meddelande istället för att skicka iväg det direkt. Det är det första argumentet som bestämmer när meddelandet ska gå iväg. Uttrycket TimeSpan.FromSeconds(3) säger att meddelandet ska gå iväg efter tre sekunder, oavsett vad klockan är. Schemaläggaren förväntar sig att man anger relativa tider, så om jag vill att meddelandet ska gå iväg ett bestämt klockslag, t.ex. den 19/5 kl. 13:30, kan jag istället skriva new DateTime(2015, 5, 19, 13, 30, 0).Subtract(DateTime.Now).

    Det andra argumentet är aktören som ta emot meddelandet. I mitt exempel finns endast en aktör, så den skickar jag in. Det tredje argumentet är meddelandet som ska skickas. Slutligen, det fjärde argumentet är avsändaren. Om det är en aktör som skickar meddelandet, skriver man normalt this.Self, men det går bra att skicka in en annan aktör här.

    I mitt fall när det inte finns någon aktör som avsändare (det är ju formuläret som skickar) så kan det vara frestande att skicka in null, men i dagsläget fungerar inte det. Klassen ActorRefs innehåller konstanter som kan användas som substitut för en avsändare, och Nobody betyder just ingen. (Detta kan komma att ändras.)

    Men tänk dig att du vill bli informerad när något inte händer. Från första klicket vill du veta om användaren fortsätter att klicka var tredje sekund, eller om användaren har blivit passiv. Det kan vi lösa på följande vis:

    1. När första klicket inträffar, säger vi att vi vill ha ett meddelande om tre sekunder.

    2. När knappen blir klickad på nästa gång, inom tre sekunder, så avbryter vi beställningen, och gör om den på nytt.

    3. Skulle användaren inte klicka inom tre sekunder så går meddelandet iväg eftersom det inte hunnit avbrytas. Således vet vi att användaren har blivit passiv.

    Detta kräver minimala förändringar. Först vill vi ha ett kvitto på beställningen. För detta använder jag en medlemsvariabel av typen Akka.Actor.ICancelable.

    private ICancelable idle_detector = null;

    Funktionen ScheduleTellOnce ger ingen retur, men ScheduleTellOnceCancelable ger ett svar i ICancelable-format som jag kan lagra i min nya medlemsvariabel. När användaren klickar, kan jag kolla om det finns en liggande beställning, och i så fall kan jag avbryta den – eftersom användaren klickat så är han ju inte inaktiv.

    private void button1_Click(object sender, EventArgs e)
    {
       if (!(this.idle_detector == null))
          this.idle_detector.Cancel();
       this.idle_detector =
          this.actorSystem.Scheduler.ScheduleTellOnceCancelable(
          TimeSpan.FromSeconds(3), this.listen,
          new TimedMessage("Idle!!!"), ActorRefs.Nobody);
    }

    För att testa programmet, starta och klicka lite då och då på knappen. För varje gång tre sekunder får passera utan något klick så går meddelandet iväg till aktören.

  • Akka .NET Remote duplex

    Akka .NET Remote duplex

    Jag har tidigare visat hur man skickar enkla meddelanden med Akka .NET Remote. Exemplet bestod av tre projekt, en server, ett datalager och en klient. Här tänkte jag visa hur man använder Akka .NET Remote för att både skicka och ta emot meddelanden. I detta exempel återanvänder jag servern BioServer och datalagret BioData från det förra exemplet.

    vs

    Tanken är alltså att programmet är ett enkelt bokningssystem för en liten biograf med tre sittplatser. Denna gång tänker jag utöka datalagret med lite nya meddelanden och servern med kapaciteten att svara på inkommande anrop. Dessutom bygger jag en ny grafisk klient.

    Datalagret
    I den förra versionen hade vi bara ett enda definierat meddelande i datalagret, klassen Bokningsmeddelande. Detta meddelande finns kvar, men nu kompletterar vi med Bokningssvar som ska skickas tillbaka till klienten som skickar in ett Bokningsmeddelande.

    public class Bokningsmeddelande
    {
       public int StolNr { get; private set; }
       public int BokadAv { get; private set; }
       public Bokningsmeddelande(int stolNr, int bokadAv)
       {
          this.StolNr = stolNr;
          this.BokadAv = bokadAv;
       }
    
       public override string ToString()
       {
          return string.Format(
             "Bokning från {0} på stol {1}.",
             this.BokadAv, this.StolNr);
       }
    }
    
    public class Bokninssvar
    {
       public bool BokningAccepterad { get; private set; }
       public int StolNr { get; private set; }
       public Bokninssvar(bool bokningAccepterad, int stolNr)
       {
          this.BokningAccepterad = bokningAccepterad;
          this.StolNr = stolNr;
       }
    }

    Servern
    Låt oss börja med att implementera stödet för Bokningsmeddelande och Bokningssvar.

    Klassen Stollista, alltså den bokningsbara resursen, har jag valt att utöka med en funktion som kan uppge vem som bokat en plats.

    public int BokadAv(int stolNr)
    {
       var stol = this.Find(m => m.StolNr == stolNr);
       if (stol == null)
          return 0;
       else
          return stol.BokadAv;
    }

    Det innebär att hela klassen nu ser ut så här:

    class Stollista : List
    {
       public override string ToString()
       {
          var ret = new System.Text.StringBuilder();
          foreach (var s in this)
             ret.AppendLine(s.ToString());
          return ret.ToString();
       }
    
       public bool Boka(int stolNr, int bokadAv)
       {
          var stol = this.Find(m => m.StolNr == stolNr);
          if (stol == null)
             return false;
          stol.BokadAv = bokadAv;
          return true;
       }
    
       public int BokadAv(int stolNr)
       {
          var stol = this.Find(m => m.StolNr == stolNr);
          if (stol == null)
             return 0;
          else
             return stol.BokadAv;
       }
    }

    Dessutom har jag modifierat aktören Bokningsaktor så att den kontrollerar huruvida den önskade stolen är bokningsbar, och meddelar klienten om bokningen accepteras. Notera hur aktören använder propertyn Sender för att kommunicera med avsändaren.

    class Bokningsaktor : Akka.Actor.ReceiveActor
    {
    
       public Bokningsaktor()
       {
          this.Receive(b => {
             Console.WriteLine("Ny bokning!");
             if (Program.stolar.BokadAv(b.StolNr) == 0 ||
                Program.stolar.BokadAv(b.StolNr) == b.BokadAv)
             {
                Program.stolar.Boka(b.StolNr, b.BokadAv);
                this.Sender.Tell(new BioData.Bokninssvar(true, b.StolNr));
             }
             else
                this.Sender.Tell(new BioData.Bokninssvar(false, b.StolNr));
             Console.WriteLine(Program.stolar.ToString());
          });
       }
    }

    Den nya klienten
    I detta fall vill jag ha en grafisk klient för att kunna representera svaren från servern på ett någorlunda snyggt sätt. Jag har skapat en applikation av typen Windows Forms Application, satt referenser till Akka .NET och Akka .NET Remote med hjälp av NuGet samt satt en referens till datalagret BioData. Dessutom vill jag läsa in namnrymden Akka.Actor för att komma åt en extension method – en överlagring av funktionen ActorOf.

    using Akka.Actor;

    Vidare väljer jag att hålla en referens till mitt aktörssystem i huvudformuläret.

    private Akka.Actor.ActorSystem system;

    På formuläret har jag placerat ut ett textfält där användaren förväntas infoga sitt användar-ID (ett heltal) samt tre knappar som representerar de tre bokningsbara stolarna. Man förväntas ange sitt ID i textfältet innan man klickar på någon knapp.

    form

    I formulärets Load-event initierar jag aktörssystemet på samma sätt som tidigare, men sparar nu referensen i medlemsvariabeln som jag deklarerade ovan.

    private void Form1_Load(object sender, EventArgs e)
    {
       var config = Akka.Configuration.ConfigurationFactory.ParseString(@"
    akka {
        actor {
            provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
        }
        remote {
            helios.tcp {
                port = 0
                hostname = localhost
            }
        }
    }
    ");
       this.system = ActorSystem.Create("Bokningsklient", config);         
    }

    Eventet FormClosed får sköta uppstädningen.

    private void Form1_FormClosed(object sender, FormClosedEventArgs e)
    {
       this.system.Shutdown();
       this.system.Dispose();
    }

    Sedan ska Click-eventet under varje knapp inleda bokningsförfarandet.

    private void btnStol1_Click(object sender, EventArgs e)
    {
       this.Boka(1);
    }
    
    private void btnStol2_Click(object sender, EventArgs e)
    {
       this.Boka(2);
    }
    
    private void btnStol3_Click(object sender, EventArgs e)
    {
       this.Boka(3);
    }

    För att aktören ska kunna komma åt knapparna direkt, har jag även en enkel funktion som tar ett stolsnummer och ger en referens till en knapp.

    public Button Stolreferens(int StolNr)
    {
       Button[] ret = { this.btnStol1, this.btnStol2, this.btnStol3 };
       return ret[StolNr - 1];
    }

    Sista funktionen i formuläret är den som utför själva bokningen. Notera att vi skickar med en referens till en lokal aktör som ska kunna ta emot svaret från servern.

    private void Boka(int StolNr)
    {
       var avsandare = this.system.ActorOf();
       var bokare = this.system.ActorSelection(
          "akka.tcp://AllaBokare@localhost:8080/user/Bokare");
       bokare.Tell(new BioData.Bokningsmeddelande(StolNr,
          int.Parse(txtAnvID.Text)), avsandare);
    }

    Den lokala aktören ser ut så här. Notera att den innehåller lite fulkod för att erhålla en referens till formuläret i fråga. Koden förmodar att formuläret är ensamt i programmet.

    public class LocalActor : Akka.Actor.ReceiveActor
    {
       public LocalActor()
       {
          this.Receive(s =>
          {
             var stol = (Application.OpenForms[0]
                as Form1).Stolreferens(s.StolNr);
             stol.BackColor = s.BokningAccepterad ? Color.Green : Color.Red;
          });
       }
    }

    Varje klient som klickar på en knapp för att utföra en bokning, kommer att få en bekräftelse genom att knappen blir grön. Om stolen inte är bokningsbar blir istället knappen röd.

    form2

  • Introduktion till Akka .NET Remote

    Introduktion till Akka .NET Remote

    Akka .NET Remote låter aktörerna i Akka .NET skicka meddelanden till andra processer genom Windows Sockets. Det innebär att ett system kan distribueras över flera processer oavsett om de ligger på samma fysiska maskin eller på en annan maskin i nätverket.

    Arkitekturen i Akka .NET Remote är serverfri – varje peer kan kontakta och kommunicera med vilken annan peer som helst, givet att namn och port är känt. Det innebär att om du bygger en server, kan den utan problem förmedla direktkontakt mellan sina klienter.

    Detta exempel visar ett mycket enkelt bokningssystem av en biosalong med tre stolar. Systemet består av tre stycken projekt, en server, ett datalager och en klient. Servern och klienten är Console Applications (C#) och datalagret är ett klassbibliotek.

    vs

    Servern
    Låt oss börja med att titta på servern. I mitt exempel heter den BioServer. All kod som visas i servern förutsätter följande:

    1. Akka .NET är installerat. Detta kan göras med följande Nuget-kommando:

    Install-Package Akka

    2. Akka .NET Remote är installerat.

    Install-Package Akka.Remote

    3. Följande using-sats:

    using Akka.Actor;

    Så här ser huvudprogrammet ut:

    class Program
    {
       public static Stollista stolar;
    
       static void Main(string[] args)
       {
          var config =
             Akka.Configuration.ConfigurationFactory.ParseString(@"
    akka {
        actor {
            provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
        }
    
        remote {
            helios.tcp {
                port = 8080
                hostname = localhost
            }
        }
    }
    ");
    
          stolar = new Stollista()
             { new Stol(1), new Stol(2), new Stol(3) };
          Console.WriteLine(stolar.ToString());
    
          using (var system =
             Akka.Actor.ActorSystem.Create("AllaBokare", config))
          {
             var bokare = system.ActorOf(new
                Akka.Actor.Props(typeof(Bokningsaktor)),"Bokare");
             Console.ReadLine();
          }
       }
    }

    Notera att konfigurationen anger vilken port servern ska lyssna på (8080). Den kodraden har jag klippt ut från dokumentationen för Akka .NET Remote och klistrat in här. Portnumret måste vara förutbestämt för att den första klienten ska kunna hitta servern.

    Därefter har jag min biosalong bestående av tre stolar. Dessa beskrivs av följande klasser:

    class Stollista : List<Stol>
    {
       public override string ToString()
       {
          var ret = new System.Text.StringBuilder();
          foreach (var s in this)
             ret.AppendLine(s.ToString());
          return ret.ToString();
       }
    
       public bool Boka(int stolNr, int bokadAv)
       {
          var stol = this.Find(m => m.StolNr == stolNr);
          if (stol == null)
             return false;
          stol.BokadAv = bokadAv;
          return true;
       }
    }

    Därefter använder jag Akka.Actor.ActorSystem.Create för att skapa mitt aktörssystem (kallad AllaBokare) på samma sätt som om jag hade tänkt att använda Akka .NET i en och samma process, och i using-blocket för aktörssystemet så skapar jag min aktör (kallad Bokare). Det som återstår i servern är klassen som beskriver aktören. Den ser ut så här:

    class Bokningsaktor : Akka.Actor.ReceiveActor
    {
    
       public Bokningsaktor()
       {
          this.Receive(b => {
             Console.WriteLine("Ny bokning!");
             Program.stolar.Boka(b.StolNr, b.BokadAv);
             Console.WriteLine(Program.stolar.ToString());
          });
       }
    }

    Datalagret
    Datalagret har jag valt att lägga i ett annat projekt i samma lösning som servern. Jag har kallat datalagret för BioData. Här finns en klass som beskriver meddelandet som klienten kan skicka till servern.

    public class Bokningsmeddelande
    {
       public int StolNr { get; private set; }
       public int BokadAv { get; private set; }
       public Bokningsmeddelande(int stolNr, int bokadAv)
       {
          this.StolNr = stolNr;
          this.BokadAv = bokadAv;
       }
    
       public override string ToString()
       {
          return string.Format("Bokning från {0} på stol {1}.",
             this.BokadAv, this.StolNr);
       }
    }

    Både servern och klienten ska ha en referens till datalagret.

    Klienten
    Klienten är mycket enkel. Notera först skillnaden i konfigurationen mellan klienten och servern. Servern har port 8080, vilket klienten måste veta om för att kunna hitta servern. Men när klienten gjort sig tillkänna så vet servern vilken port klienten använder, därför behöver vi inte bestämma någon port åt klienten. Att ange port 0 är att begära dynamisk port.

    Därefter skapar vi ett aktörssystem med det godtyckliga namnet Bokningsklient. Från systemet i klienten väljer vi att ansluta oss till servern genom att anropa ActorSelection. Som argument använder jag sökvägen till aktören på servern (som hette Bokare i systemet AllaBokare). Slutligen samlar jag in information från slutanvändaren som skickas till servern med metoden Tell. Så här ser källkoden för klienten ut:

    class Program
    {
       static void Main(string[] args)
       {
          var config = Akka.Configuration.ConfigurationFactory.ParseString(@"
    akka {
        actor {
            provider = ""Akka.Remote.RemoteActorRefProvider, Akka.Remote""
        }
        remote {
            helios.tcp {
                port = 0
                hostname = localhost
            }
        }
    }
    ");
    
          using (var system =
             Akka.Actor.ActorSystem.Create("Bokningsklient", config))
          {
             var bokare = system.ActorSelection(
                "akka.tcp://AllaBokare@localhost:8080/user/Bokare");
             Console.Write("Vad är ditt användar-ID? Skriv ett heltal. ");
             var userId = int.Parse(Console.ReadLine());
             while(true)
             {
                Console.Write("Vilken stol vill du boka? (1-3) ");
                var stol = Console.ReadLine();
                if (stol == "")
                   break;
                bokare.Tell(new
                   BioData.Bokningsmeddelande(int.Parse(stol), userId));
             }
             system.Shutdown();
          }
       }
    }

    Bilden visar servern bakom en klient.

    akka_remote

    Eftersom portnumret tilldelas till klienten dynamiskt så kan flera klienter startas och boka platser. Och eftersom arkitekturen i Akka .NET Remote är peer-to-peer så kan dessa skicka meddelanden till varandra.

  • Enkel trådning i .NET med Akka

    Enkel trådning i .NET med Akka

    Historiskt sett har det varit ganska komplicerat att bygga multitrådade applikationer i .NET. Rent tekniskt var det enkelt att sjösätta en tråd med hjälp av klassen System.Threading.Thread, men om mycket skulle göras började koden ganska snabbt likna spaghetti. För att minska ner koden kan man använda multitrådade delegater istället för objekt av typen Thread, men det löste inte problemet med spaghettikod. Nyckelorden asynk och await löste både komplexiteten med hopp och returvärde, men var ganska hårt knutet till modellen TAP.

    Om jag väljer att jobba med Akka .NET blir trådningen ännu mer hanterad, fast kring en mycket friare modell än TAP, nämligen Actor Model. (I alla nedanstående exempel jobbar jag i Visual Studio 2013.) Tänk dig att jobbar i Windows Forms och att du vill utföra något som kräver ungefär fem sekunders arbete och sedan visar ett meddelande när arbetet är utfört. Min erfarenhet är att Akka .NET fortfarande kan utsättas för förändringar i sitt publika API, så den exakta kodens utseende kan variera – jag kör Akka 1.0.0. För att visa detta skapar jag ett helt nytt Windows Forms-projekt (C# med .NET Framework 4.5.1) och installerar Akka .NET från Package Manager Console genom att skriva:

    Install-Package Akka

    Om du inte ser Package Manager Console, Klicka Tools, NuGet Package Manager och Package Manager Console.

    Akka .NET använder meddelanden för att kommunicera. Meddelanden är kort och gott godtyckliga objekt som initieras och skickas i samband med att ett asynkront anrop görs. Meddelandet bör vara immutable, alltså meddelandet ska inte kunna modifieras, vilket kan implementeras genom att objektets data inte har några publika setters.

    class AkkaMessage
    {
       public string MyValue { get; private set; }
    
       public AkkaMessage(string myValue)
       {
          this.MyValue = myValue;
       }
    }

    En actor (aktör) är den t.ex. det objekt som kan ta emot ett meddelande och agera på det. Det som utmärker en sådan aktör är basklassen Akka.Actor.ReceiveActor samt konstruktorn som tar emot meddelandet som skickas till den. För att ta emot meddelandet används den generiska metoden Receive vars argument är det som ska göras med meddelandet. Den generiska metodens typparameter är meddelandeklassen. Så i följande kod anropar vi Receive och skickar med kod jobbar i fem sekunder för att sedan visa en meddelanderuta.

    class AkkaActor : Akka.Actor.ReceiveActor
    {
       public AkkaActor()
       {
          this.Receive(x =>
             {
                System.Threading.Thread.Sleep(5000);
                System.Windows.Forms.MessageBox.Show(x.MyValue);
             }
          );
       }
    }

    Nu återstår bara initieringen och själva anropet. Följande kod har jag skrivit i mitt programs huvudformulär. Först har jag ett privat fält av typen Akka.Actor.ActorSystem som representerar motorn i Akka – det s.k. actorsystemet. I Load-eventet för formuläret initierar jag systemet med funktionen Create. Create vill ha ett namn på systemet i sin kostruktor. Slutligen, under Click-eventet på en knapp på formuläret så gör jag anropet som ska hanteras av aktören ovan.

    Den första raden anropar funktionen ActorOf för att erhålla en referens till en aktör. Den andra raden utför utför anropet med hjälp av funktionen Tell, och låter ett meddelande skickas med som argument. Meddelandet måste vara av typen AkkaMessage, eftersom jag hårdkodade aktören att hantera den typen.

    public partial class Form1 : Form
    {
       private Akka.Actor.ActorSystem actorSystem;
    
       public Form1()
       {
          InitializeComponent();
       }
    
       private void Form1_Load(object sender, EventArgs e)
       {
          this.actorSystem =
             Akka.Actor.ActorSystem.Create("ActorSystem");
       }
    
       private void button1_Click(object sender, EventArgs e)
       {
          var m = this.actorSystem.ActorOf(new
             Akka.Actor.Props(typeof(AkkaActor)));
          m.Tell(new AkkaMessage("Hello!"), m);
       }
    }

    När du kör programmet, notera att programmets fönster är svarsbenäget under den tid programmet pausar pausar (anropet på Sleep), vilket säger oss att aktören och formuläret körs på olika trådar.

    Om du istället hade velat implementera detta i Visual Basic, skulle ditt meddelande kunna se ut så här:

    Public Class AkkaMessage
       Public Property MyValue() As String
       Public Sub New(MyValue As String)
          Me.MyValue = MyValue
       End Sub
    End Class

    Aktören skulle kunna se ut så här:

    Public Class AkkaActor
       Inherits Akka.Actor.ReceiveActor
    
       Public Sub New()
          Me.Receive(Of AkkaMessage)(AddressOf MyMessageHandler)
       End Sub
    
       Private Sub MyMessageHandler(X As AkkaMessage)
          System.Threading.Thread.Sleep(5000)
          MessageBox.Show(X.MyValue)
       End Sub
    
    End Class

    Och slutligen, formuläret skulle kunna se ut så här:

    Public Class Form1
    
       Private ActorSystem As Akka.Actor.ActorSystem
    
       Private Sub Form1_Load(sender As Object, _
          e As EventArgs) Handles MyBase.Load
          Me.ActorSystem = Akka.Actor.ActorSystem.Create("ActorSystem")
       End Sub
    
       Private Sub Button1_Click(sender As Object, _
          e As EventArgs) Handles Button1.Click
          Dim M = Me.ActorSystem.ActorOf( _
             New Akka.Actor.Props(GetType(AkkaActor)))
          M.Tell(New AkkaMessage("Hello!"), M)
       End Sub
    
    End Class