NamedPipeServerStream / NamedPipeClientStream包装器

时间:2017-11-23 12:55:50

标签: c# multithreading named-pipes

我目前正在为NamedPipeServerStream / NamedPipeClientStream编写一个完整Event的小包装器,与使用AsyncCallbacks相反。

我公开了几乎所有可能的同步和异步方法(连接/等待连接,写入等),所以如果消费者想要,例如,启动服务器实例并在客户端连接时发送消息,他可以去完全同步路线并做一些像......

var server = new NamedPipeServer("helloWorld");
server.StartAndWait();
server.Write("Welcome!");

或像...一样的异步方式。

var server = new NamedPipeServer("helloWorld);
server.ClientConnected += x => x.WriteAsync("Welcome!");
server.Start(); //Start() returns immediately

然而,我正在努力寻找一种好方法来阅读信息。当前读取消息时,我触发MessageAvailable事件并将消息作为其中一个参数传递。

我无法想出一种实现同步读取的正确方法。

我考虑的是以下内容:

使用GetNextMessage()同步方法获取消息。在内部,这可以通过两种不同的方式处理:

  • 我可以保留IEnumerable<Message>所有尚未消费的消息。所以只要对方发送消息,我就会从流中读取它并将其存储在内存中,以便以后可以GetNextMessage()使用它们。优点是,一旦写入消息,它就会释放流,因此它不会阻止另一方发送其他消息。缺点是我完全无法控制我将持有多少消息或它们的大小。我的IEnumerable<Message>最终可能会有10GB的非消费消息,而且由于我不能强迫消费者检索消息,所以我无能为力。

  • 我可以认为我只将一条消息存储在内部缓冲区中,并且只有在通过GetNextMessage()消耗了一条消息后再次开始阅读。如果我这样做,另一方将被阻止编写其他消息,直到前一个消息被消耗。更确切地说,另一方可以写入直到流已满。这可能是多个小的完整消息或一个不完整的消息。在不完整的单个消息的情况下,我认为这是一种更糟糕的方法,因为在发送的消息的第1部分和后续部分之间,另一端可能最终断开连接并且整个消息将丢失。

为了使事情更难,在上述任何一种方法中,消费者总是有可能使用事件来接收消息(记住事件包含收到的消息),因此不需要GetNextMessage()。我要么必须完全停止在事件中发送消息,要么找到一种不通过事件消耗消息将事件推送到内部缓冲区的方法。虽然我可以很容易地判断是否存在事件处理程序,但是无法知道消息是否实际在那里被处理(即,考虑实现这个消息的类并监听该事件,但对它没有任何作用)。我在这里看到的唯一真正的方法是从事件中删除消息,强迫消费者始终致电GetNextMessage(),但我愿意接受其他想法。

这两种方法都存在另一个问题,即如果使用WriteAsync()(或Write()使用不同的话,我无法控制消息的发送顺序线程)。

有人能想出更好的方法来解决这个问题吗?

1 个答案:

答案 0 :(得分:1)

我建议采用以下方法。创建界面:

public interface ISubscription : IDisposable {
    Message NextMessage(TimeSpan? timeout);
}

public class Message {

}

然后执行:

public class NamedPipeServer {        
    public void StartAndWait() {

    }

    public ISubscription StartAndSubscribe() {
        // prevent race condition before Start and subscribing to MessageAvailable
        var subscription = new Subscription(this);
        StartAndWait();
        return subscription;
    }

    public ISubscription Subscribe() {
        // if user wants to subscribe and some point after start - why not
        return new Subscription(this);
    }

    public event Action<Message> MessageAvailable;

    private class Subscription : ISubscription {
        // buffer
        private readonly BlockingCollection<Message> _queue = new BlockingCollection<Message>(
            new ConcurrentQueue<Message>());

        private readonly NamedPipeServer _server;

        public Subscription(NamedPipeServer server) {
            // subscribe to event
            _server = server;
            _server.MessageAvailable += OnMessageAvailable;
        }

        public Message NextMessage(TimeSpan? timeout) {
            // this is blocking call
            if (timeout == null)
                return _queue.Take();
            else {
                Message tmp;
                if (_queue.TryTake(out tmp, timeout.Value))
                    return tmp;
                return null;
            }
        }

        private void OnMessageAvailable(Message msg) {
            // add to buffer
            _queue.Add(msg);
        }

        public void Dispose() {
            // clean up
            _server.MessageAvailable -= OnMessageAvailable;
            _queue.CompleteAdding();
            _queue.Dispose();
        }
    }
}

客户然后拨打SubscribeStartAndSubscribe

var sub = server.StartAndSubscribe();
var message = sub.NextMessage();
var messageOrNull = sub.NextMessage(TimeSpan.FromSeconds(1));
sub.Dispose();

如果没有人订阅那样 - 你不缓冲任何消息。如果有人订阅然后不消费 - 这是他们的问题,而不是你的问题,因为缓冲发生在他们现在拥有的订阅中。您还可以限制_queue阻止收集的大小,然后添加到它会阻止达到限制,阻止您的MessageAvailable事件,但我不建议这样做。