我的Akka.Net演示速度非常慢

时间:2016-03-24 21:57:59

标签: akka.net

我正在尝试使用akka.net运行概念证明。我确信我做的事情非常糟糕,但我无法弄清楚它是什么。

我希望我的演员形成节点图。后来,这将是一个复杂的业务对象图,但是现在我想尝试一个简单的线性结构:

enter image description here

我想问一个节点是否有9步之遥的邻居。我试图以递归方式实现它。我向节点#9询问距离为9步的邻居,然后我向节点#8询问距离为8步的邻居,依此类推。最后,这应该返回节点#0作为答案。

嗯,我的代码有效,但执行时需要 4秒。那是为什么?

这是我的完整代码清单:

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);
        }
    }
}

1 个答案:

答案 0 :(得分:5)

这种行为缓慢的原因是你使用Ask(你使用它的方式,但我稍后将介绍)。在您的示例中,您在循环中询问每个邻居,然后立即执行response2.Result,这会主动阻止当前actor(以及它所驻留的线程)。所以你实际上是通过阻塞来实现同步流。

最容易解决的问题是收集从Ask返回的所有任务,并使用Task.WhenAll收集所有任务,而无需等待循环中的每个任务。举个例子:

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()));
            }
        });
    }
}

这个更快。

注意:一般情况下,你不应该在演员内部使用Ask:

  1. 每个ask都在当前actor中分配一个监听器,因此通常使用Ask比使用Tell传递消息要重。
  2. 当通过演员链发送消息时,询问成本还通过每个演员传输两次消息(一个用于请求,一个用于回复)。其中一种流行的模式是,当您从A⇒B⇒C⇒D发送请求并从D回复到A时,您可以直接回复D⇒A,而无需通过整个链回传递消息。通常Forward / Tell的组合效果更好。
  3. 一般情况下,如果没有必要,请不要使用接收的异步版本 - 此时,与同步版本相比,演员的速度会慢一些。