简体   繁体   English

Akka.Net流和远程处理(Sink.ActorRefWithAck)

[英]Akka.Net Streams and remoting (Sink.ActorRefWithAck)

I've made quite a simple implementation with Akka.net Streams using Sink.ActorRefWithAck : a subscriber asks for a large string to a publisher which sends it by slices. 我已经使用Sink.ActorRefWithAck使用Akka.net Streams进行了非常简单的实现:订阅者向发布者Sink.ActorRefWithAck一个大字符串,然后按分片发送它。 It works perfectly fine locally (UT) but not remotely . 它在本地(UT)可以正常运行,但不能在远程运行 And I cannot understand what's wrong? 我不明白怎么了? Concretly: the subscriber is able to send the request to the publisher which responds with an OnInit message but then the OnInit.Ack will never goes back to the publisher. 确切地说:订阅者可以将请求发送给发布者,发布者以OnInit消息作为响应,但是OnInit.Ack将永远不会返回发布者。 This Ack message ends up as a dead letter : Ack消息最终以空字母结尾:

INFO  Akka.Actor.EmptyLocalActorRef - Message Ack from akka.tcp://OutOfProcessTaskProcessing@localhost:12100/user/Streamer_636568240846733287 to akka://OutOfProcessTaskProcessing/user/StreamSupervisor-0/StageActorRef-0 was not delivered. 1 dead letters encountered.

Note that the log is from the destination actor so the message is handled in the right process. 请注意,日志来自目标参与者,因此消息将在正确的过程中处理。 There is no obvious path error. 没有明显的路径错误。

Looking at the publisher code which does not handle this message, I really don't know what I'm doing wrong: 查看无法处理此消息的发布者代码,我真的不知道自己在做什么错:

    public static void ReplyWithStreamedString(IUntypedActorContext context, string toStream, int chunkSize = 2000)
    {
        Source<string, NotUsed> source = Source.From(toStream.SplitBy(chunkSize));
        source.To(Sink.ActorRefWithAck<string>(context.Sender, new StreamMessage.OnInit(),
                new StreamMessage.OnInit.Ack(),
                new StreamMessage.Completed(),
                exception => new StreamMessage.Failure(exception.Message)))
            .Run(context.System.Materializer());
    }

Here is the subscriber code: 这是订户代码:

public static Task<string> AskStreamedString(this ICanTell self, object message, ActorSystem context, TimeSpan? timeout = null)
    {
        var tcs = new TaskCompletionSource<string>();
        if (timeout.HasValue)
        {
            CancellationTokenSource ct = new CancellationTokenSource(timeout.Value);
            ct.Token.Register(() => tcs.TrySetCanceled());
        }

        var props = Props.Create(() => new StreamerActorRef(tcs));
        var tempActor = context.ActorOf(props, $"Streamer_{DateTime.Now.Ticks}");

        self.Tell(message, tempActor);

        return tcs.Task.ContinueWith(task =>
        {
            context.Stop(tempActor);
            if(task.IsCanceled)
                throw new OperationCanceledException();
            if (task.IsFaulted)                    
                throw task.Exception.GetBaseException();
            return task.Result;
        });
    }

    internal class StreamerActorRef : ReceiveActor
    {
        readonly TaskCompletionSource<string> _tcs;

        private readonly StringBuilder _stringBuilder = new StringBuilder();

        public StreamerActorRef(TaskCompletionSource<string> tcs)
        {
            _tcs = tcs;
            Ready();
        }

        private void Ready()
        {
            ReceiveAny(message =>
            {
                switch (message)
                {
                    case StreamMessage.OnInit _:
                        Sender.Tell(new StreamMessage.OnInit.Ack());
                        break;
                    case StreamMessage.Completed _:
                        string result = _stringBuilder.ToString();
                        _tcs.TrySetResult(result);
                        break;
                    case string slice:
                        _stringBuilder.Append(slice);
                        Sender.Tell(new StreamMessage.OnInit.Ack());
                        break;
                    case StreamMessage.Failure error:
                        _tcs.TrySetException(new InvalidOperationException(error.Reason));
                        break;
                }
            });
        }
    }

With messages: 带有消息:

public class StreamMessage
{
        public class OnInit
        {
            public class Ack{}
        }

        public class Completed { }

        public class Failure
        {
            public string Reason { get; }

            public Failure(string reason)
            {
                Reason = reason;
            }
        }
    }

In general sources and sinks working with actor refs have not been designed to work over remote connections - they don't cover message retries, which can cause deadlocks in your system if some stream control message won't be passed in. 通常,使用actor ref的源和接收器的设计都不能通过远程连接进行工作-它们不包含消息重试,如果某些流控制消息无法传入,则可能导致系统死锁。

The feature you're looking for is called StreamRefs (which works like actor refs, but for streams), and will be shipped as part of v1.4 release (see github pull request for more details). 您要查找的功能称为StreamRefs (其作用类似于actor ref,但适用于流),并将作为v1.4版本的一部分提供(有关更多详细信息,请参见github pull request )。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM