2016-03-24 4 views
2

Ich versuche, einen Beweis des Konzepts mit akka.net laufen zu lassen. Ich bin sicher, dass ich etwas schrecklich falsch mache, aber ich kann nicht herausfinden, was es ist.Meine Akka.Net Demo ist unglaublich langsam

Ich möchte, dass meine Akteure ein Diagramm von Knoten bilden. Später wird dies eine komplexe grafische Darstellung von Geschäft objekt sein, aber jetzt mag ich eine einfache lineare Struktur wie folgt versuchen:

enter image description here

Ich mag einen Knoten für einen Nachbarn bitten, die 9 Schritte entfernt ist. Ich versuche das rekursiv zu implementieren. Ich frage Knoten # 9 nach einem Nachbarn, der 9 Schritte entfernt ist, dann frage ich Knoten # 8 nach einem Nachbarn, der 8 Schritte entfernt ist und so weiter. Schließlich sollte dies den Knoten # 0 als Antwort zurückgeben.

Nun, mein Code funktioniert, aber es dauert mehr als 4 Sekunden auszuführen. Warum das?

Dies ist meine vollständige Codeauflistung:

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using Akka; 
using Akka.Actor; 

namespace AkkaTest 
{ 
    class Program 
    { 
     public static Stopwatch stopwatch = new Stopwatch(); 
     static void Main(string[] args) 
     { 
      var system = ActorSystem.Create("MySystem"); 

      IActorRef[] current = new IActorRef[0]; 

      Console.WriteLine("Initializing actors..."); 

      for (int i = 0; i < 10; i++) 
      { 
       var current1 = current; 
       var props = Props.Create<Obj>(() => new Obj(current1, Guid.NewGuid())); 
       var actorRef = system.ActorOf(props, i.ToString()); 
       current = new[] { actorRef }; 
      } 
      Console.WriteLine("actors initialized."); 

      FindNeighboursRequest r = new FindNeighboursRequest(9); 

      stopwatch.Start(); 

      var response = current[0].Ask(r); 
      FindNeighboursResponse result = (FindNeighboursResponse)response.Result; 
      stopwatch.Stop(); 
      foreach (var d in result.FoundNeighbours) 
      { 
       Console.WriteLine(d); 
      } 

      Console.WriteLine("Search took " + stopwatch.ElapsedMilliseconds + "ms."); 
      Console.ReadLine(); 
     } 
    } 
    public class FindNeighboursRequest 
    { 
     public FindNeighboursRequest(int distance) 
     { 
      this.Distance = distance; 
     } 
     public int Distance { get; private set; } 
    } 

    public class FindNeighboursResponse 
    { 
     private IActorRef[] foundNeighbours; 

     public FindNeighboursResponse(IEnumerable<IActorRef> descendants) 
     { 
      this.foundNeighbours = descendants.ToArray(); 
     } 

     public IActorRef[] FoundNeighbours 
     { 
      get { return this.foundNeighbours; } 
     } 
    } 


    public class Obj : ReceiveActor 
    { 
     private Guid objGuid; 
     readonly List<IActorRef> neighbours = new List<IActorRef>(); 
     public Obj(IEnumerable<IActorRef> otherObjs, Guid objGuid) 
     { 
      this.neighbours.AddRange(otherObjs); 
      this.objGuid = objGuid; 
      Receive<FindNeighboursRequest>(r => handleFindNeighbourRequest(r)); 
     } 

     public Obj() 
     { 
     } 

     private async void handleFindNeighbourRequest (FindNeighboursRequest r) 
     { 
      if (r.Distance == 0) 
      { 
       FindNeighboursResponse response = new FindNeighboursResponse(new IActorRef[] { Self }); 
       Sender.Tell(response, Self); 
       return; 
      } 

      List<FindNeighboursResponse> responses = new List<FindNeighboursResponse>(); 

      foreach (var actorRef in neighbours) 
      { 
       FindNeighboursRequest req = new FindNeighboursRequest(r.Distance - 1); 
       var response2 = actorRef.Ask(req); 
       responses.Add((FindNeighboursResponse)response2.Result); 
      } 

      FindNeighboursResponse response3 = new FindNeighboursResponse(responses.SelectMany(rx => rx.FoundNeighbours)); 
      Sender.Tell(response3, Self); 
     } 
    } 
} 
+0

Können Sie die Stoppuhr nach dem Erstellen des ActorSystem starten und es erneut versuchen? –

+0

Das ist genau das, was der Code tut - oder zumindest, was ich beabsichtigt habe. Ich starte die Stoppuhr _nach der 'for (int i = 0; i <10; i ++) Schleife. – user1691896

+0

Oh ja, ich habe es falsch gelesen. Wie Horusiath sagte, Schauspieler sollten nicht blockieren, Sie erleben einen Threadpool-Hunger. –

Antwort

4

Der Grund solcher langsamen Verhalten ist die Art und Weise Sie Fragen verwenden (ein, dass Sie es verwenden, aber ich werde diese später) erfolgen. In Ihrem Beispiel fragen Sie jeden Nachbarn in einer Schleife und führen dann sofort response2.Result aus, wodurch der aktuelle Akteur (und der Thread, auf dem er sich befindet) aktiv blockiert wird. Sie machen also im Wesentlichen synchronen Fluss mit Blockierung.

Die einfachste Sache, das zu beheben, ist, alle Aufgaben zu sammeln, die von Ask zurückgegeben werden, und Task.WhenAll zu verwenden, um sie alle zu sammeln, ohne auf jede in einer Schleife zu warten. Nehmen wir dieses Beispiel:

public class Obj : ReceiveActor 
{ 
    private readonly IActorRef[] _neighbours; 
    private readonly Guid _id; 

    public Obj(IActorRef[] neighbours, Guid id) 
    { 
     _neighbours = neighbours; 
     _id = id; 
     Receive<FindNeighboursRequest>(async r => 
     { 
      if (r.Distance == 0) Sender.Tell(new FindNeighboursResponse(new[] {Self})); 
      else 
      { 
       var request = new FindNeighboursRequest(r.Distance - 1); 
       var replies = _neighbours.Select(neighbour => neighbour.Ask<FindNeighboursResponse>(request)); 
       var ready = await Task.WhenAll(replies); 
       var responses = ready.SelectMany(x => x.FoundNeighbours); 
       Sender.Tell(new FindNeighboursResponse(responses.ToArray())); 
      } 
     }); 
    } 
} 

Dieser ist viel schneller.

HINWEIS:

  1. Jeder fragen zuteilt einen Zuhörer innerhalb aktuellen Schauspieler, so im Allgemeinen Ask Verwendung ist viel schwerer als Weitergabe von Nachrichten mit: Im Allgemeinen sollten Sie nicht innerhalb eines Akteurs Stellen Sie verwenden Tell.
  2. Beim Senden von Nachrichten über eine Kette von Akteuren transportiert die Kosten von Ask zusätzlich Nachricht zweimal (eine für die Anfrage und eine für die Antwort) durch jeden Akteur. Eines der gängigen Muster besteht darin, dass Sie beim Senden einer Anfrage von A an die Adresse A direkt antworten können, ohne dass Sie die Nachricht über die gesamte Kette zurückgeben müssen. Normalerweise funktioniert die Kombination von Forward/Tell besser. im Moment, es ist langsamer für einen Schauspieler im Vergleich zu synchronisieren Version -
  3. Im Allgemeinen nicht Asynchron-Version benachrichtigen, wenn es nicht nötig.
+0

Große Antwort, Horusiath. Ich werde versuchen, meine Implementierung in das Muster A⇒B⇒C⇒D und dann direkt von D zurück zu A zu ändern. Ich habe jedoch ein Problem mit diesem Ansatz. Wie weiß Schauspieler A, dass er alle Antworten zurückbekommen hat? – user1691896

+0

Um sicherzustellen, dass alle erforderlichen Knoten durchlaufen wurden, müssen Sie die Menge der Knoten und deren Tiefe verfolgen und sie dann aus dieser Gruppe entfernen, sobald sie reagieren. Bei anderen Anforderungen (d. H. Nur bis zu X-Knoten) wäre der Forward/Tell-Back-Ansatz praktikabler, aber ohne zusätzliche Beschränkungen scheint die Verwendung von Ask hier gerechtfertigt zu sein. – Horusiath

Verwandte Themen