Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/307.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# NamedPipeServerStream/NamedPipeClientStream包装器_C#_Multithreading_Named Pipes - Fatal编程技术网

C# NamedPipeServerStream/NamedPipeClientStream包装器

C# NamedPipeServerStream/NamedPipeClientStream包装器,c#,multithreading,named-pipes,C#,Multithreading,Named Pipes,我目前正在为NamedPipeServerStream/NamedPipeClientStream编写一个小包装,它完全基于事件,而不是使用异步回调 我公开了几乎所有可能的同步和异步方法(连接/等待连接、写入等),因此,如果消费者想要,例如,启动服务器实例并在客户端连接时发送消息,他可以走完全同步路线并执行以下操作 var server = new NamedPipeServer("helloWorld"); server.StartAndWait(); server.Write("Welcom

我目前正在为
NamedPipeServerStream
/
NamedPipeClientStream
编写一个小包装,它完全基于
事件
,而不是使用
异步回调

我公开了几乎所有可能的同步和异步方法(连接/等待连接、写入等),因此,如果消费者想要,例如,启动服务器实例并在客户端连接时发送消息,他可以走完全同步路线并执行以下操作

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()
sync方法。在内部,这可以通过两种不同的方式处理:

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

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

更困难的是,在上述任何一种方法中,消费者总是有可能使用事件来接收消息(记住事件包含接收到的消息),因此不需要
GetNextMessage()
。我需要完全停止在事件中发送消息,或者找到一种方法,如果消息是通过事件消耗的,则不将事件推送到内部缓冲区。虽然我可以很容易地判断是否有一个事件处理程序,但没有办法知道消息是否实际上在那里处理(也就是说,考虑一个类实现这一个,并听那个事件,但不做任何事情)。我在这里看到的唯一真正的方法是从事件中删除消息,强制消费者始终调用
GetNextMessage()
,但我愿意接受其他想法

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


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

我建议采用以下方法。创建接口:

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

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

在有人订阅您的
MessageAvailable
之前收到的消息会发生什么情况?它们刚刚丢失了吗?目前没有
GetNextMessage()
,是的。如果有
GetNextMessage()
则没有。但这也是客户端和服务器都有
Start()
方法的原因。因此,您可以在尝试连接之前订阅活动。这同样适用于所有其他事件,尽管
已连接
已断开
等。。。这个想法是,如果您关心它,您可以在调用
Start()
之前订阅它,而不是使用GetNextMessage,它将返回IEnumerable。对该可枚举项进行迭代将阻止,直到有新消息可用。这仍然会使我处于一种情况,即我可能最终在本地存储10GB的消息,因为我无法控制客户端何时使用它们。是的,但这是不可避免的,这是客户端的问题。或者你们的意思是你们担心你们应该一直存储它们,即使客户端并没有调用GetMessageStream?嗯。。。看起来唯一的区别是您添加了一个订阅机制,并且您只在有人订阅时使用缓冲区。我有点同意,但是。。。A) 这似乎在一定程度上影响了命名管道默认的工作方式。我认为NamedPipes的级别很低,根本没有这个级别,我希望我的包装器也保持低级别。B) 当您创建一个具有双向通信的命名管道时,For me订阅是隐含的。但是您已经想要
GetNextMessage
阻止调用了,那么为什么它会影响命名管道的工作方式呢?正如您所说的,这是同一个调用,但只有在必要时才会执行缓冲。当您创建一个名为dpipe的双向通信时,就意味着向我添加了一个层(订阅),这似乎是在添加另一层(订阅)。这可能是一场灾难
var sub = server.StartAndSubscribe();
var message = sub.NextMessage();
var messageOrNull = sub.NextMessage(TimeSpan.FromSeconds(1));
sub.Dispose();