我的 Akka.Net 演示非常慢

My Akka.Net Demo is incredibly slow

我正在尝试获得概念验证 运行 akka.net。我确信我做错了什么,但我不知道它是什么。

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

我想向一个节点询问9步远的邻居。我正在尝试以递归方式实现它。我向节点 #9 询问 9 步远的邻居,然后我向节点 #8 询问 8 步远的邻居,依此类推。最后,这应该 return 节点 #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);
        }
    }
}

这种缓慢行为的原因是您使用 Ask 的方式(您使用它,但我稍后会介绍)。在您的示例中,您在循环中询问每个邻居,然后立即执行 response2.Result 主动阻塞当前参与者(及其所在的线程)。因此,您实际上是在使用阻塞进行同步流。

解决这个问题最简单的方法是收集从 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()));
            }
        });
    }
}

这个要快得多。

注意:通常你不应该在 actor 内部使用 Ask:

  1. 每个请求都会在当前 actor 中分​​配一个监听器,所以通常使用 Ask 比使用 Tell.
  2. 传递消息要重很多
  3. 当通过 actor 链发送消息时,ask 的成本是通过每个 actor 额外传输消息两次(一次用于请求,一次用于回复)。一种流行的模式是,当您从 A⇒B⇒C⇒D 发送请求并从 D 返回 A 时,您可以直接回复 D⇒A,而无需通过整个链返回消息。通常 Forward/Tell 的组合效果更好。
  4. 如果没有必要,通常不要使用 Receive 的异步版本 - 目前,与同步版本相比,Actor 的速度较慢。