如何使用Akka.Net?

时间:2016-02-16 14:13:00

标签: akka.net

我在Akka.Net中有一个演员级别,我想知道我是否选择了正确的方式来做某事,或者是否有更好/更简单的方法来实现我想要的。

我的具体示例是我构建一个用户 actor以响应用户登录系统,并且在构建此actor时,我需要两个数据才能完成演员的建设。

如果这是常规的.NET代码,我可能会有以下内容......

public Task<User> LoadUserAsync (string username)
{
  IProfileService profileService = ...;
  IMessageService messageService = ...;

  var loadProfileTask = profileService.GetUserProfileAsync(username);
  var loadMessagesTask = messageService.GetMessagesAsync(username);

  Task.WaitAll(loadProfileTask, loadMessagesTask);

  // Now construct the user from the result of both tasks
  var user = new User
  {
    Profile = loadProfileTask.Result,
    Messages = loadMessagesTask.Result
  }

  return Task.FromResult(user);
}

这里我使用WaitAll等待下级任务完成,并让它们同时运行。

我的问题是 - 如果我想在Akka.Net中做同样的事情,以下是最常用的方法吗?我在图片中创造了以下内容......

Actor Hierarchy

当我创建User actor时,我构建了一个(临时)User Loader Actor,其工作是通过调用Profile actor和Messages actor获取完整的用户详细信息。获取数据的叶子演员如下......

public class UserProfileLoader : ReceiveActor
{
    public UserProfileLoader()
    {
        Receive<LoadUserRequest>(msg =>
        {
            // Load the user profile from somewhere
            var profile = new UserProfile();

            // And respond to the Sender
            Sender.Tell(profile);
            Self.Tell(PoisonPill.Instance);
        });
    }
}

public class UserMessagesLoader : ReceiveActor
{
    public UserMessagesLoader()
    {
        Receive<LoadUserRequest>(msg =>
        {
            // Load the messages from somewhere
            var messages = new List<Message>();

            // And respond to the Sender
            Sender.Tell(messages);
            Self.Tell(PoisonPill.Instance);
        });
    }
}

从这次讨论中获取数据的位置并不重要,但两者都只是通过返回一些数据来响应请求。

然后我有演员协调两个数据收集演员......

public class UserLoaderActor : ReceiveActor
{
    public UserLoaderActor()
    {
        Receive<LoadUserRequest>(msg => LoadProfileAndMessages(msg));
        Receive<UserProfile>(msg =>
        {
            _profile = msg;
            FinishIfPossible();
        });

        Receive<List<Message>>(msg =>
        {
            _messages = msg;
            FinishIfPossible();
        });
    }

    private void LoadProfileAndMessages(LoadUserRequest msg)
    {
        _originalSender = Sender;
        Context.ActorOf<UserProfileLoader>().Tell(msg);
        Context.ActorOf<UserMessagesLoader>().Tell(msg);
    }

    private void FinishIfPossible()
    {
        if ((null != _messages) && (null != _profile))
        {
            _originalSender.Tell(new LoadUserResponse(_profile, _messages));
            Self.Tell(PoisonPill.Instance);
        }
    }

    private IActorRef _originalSender;
    private UserProfile _profile;
    private List<Message> _messages;
}

这只会创建两个从属演员,向他们发送消息以便破解,然后在将所有已收集的数据发送回原始请求者之前等待两者都做出响应。

那么,这是否是一种协​​调两种不同反应的合理方式,以便将它们结合起来?有没有比这更好的方法呢?

提前感谢您的回复!

3 个答案:

答案 0 :(得分:2)

感谢大家,所以我现在根据罗杰和杰夫的建议将演员简化为以下内容...

public class TaskBasedUserLoader : ReceiveActor
{
    public TaskBasedUserLoader()
    {
        Receive<LoadUserRequest>(msg => LoadProfileAndMessages(msg));
    }

    private void LoadProfileAndMessages(LoadUserRequest msg)
    {
        var originalSender = Sender;
        var loadPreferences = this.LoadProfile(msg.UserId);
        var loadMessages = this.LoadMessages(msg.UserId);

        Task.WhenAll(loadPreferences, loadMessages)
            .ContinueWith(t => new UserLoadedResponse(loadPreferences.Result, loadMessages.Result), 
                TaskContinuationOptions.AttachedToParent & TaskContinuationOptions.ExecuteSynchronously)
            .PipeTo(originalSender);
    }

    private Task<UserProfile> LoadProfile(string userId)
    {
        return Task.FromResult(new UserProfile { UserId = userId });
    }

    private Task<List<Message>> LoadMessages(string userId)
    {
        return Task.FromResult(new List<Message>());
    }
}

LoadProfile和LoadMessages方法最终将调用存储库来获取数据,但是现在我有一种简洁的方法来做我想要的。

再次感谢!

答案 1 :(得分:1)

恕我直言,这是一个有效的过程,当你分叉行动然后加入它。

顺便说一句,你可以使用this.Self.GracefulStop(new TimeSpan(1));而不是发送毒丸。

答案 2 :(得分:0)

您可以使用Ask,WhenAll和PipeTo的组合:

var task1 = actor1.Ask<Result1>(request1);
var task2 = actor2.Ask<Result2>(request2);

Task.WhenAll(task1, task2)
    .ContinueWith(_ => new Result3(task1.Result, task2.Result))
    .PipeTo(Self);

...

Receive<Result3>(msg => { ... });