通过命名管道访问单例会创建第二个单例。未在主机端触发的事件

Posted

技术标签:

【中文标题】通过命名管道访问单例会创建第二个单例。未在主机端触发的事件【英文标题】:Access Singleton via Named pipe creates second singleton. Events not fired on host side 【发布时间】:2017-02-15 10:28:50 【问题描述】:

我有两个进程,第二个进程需要在第一个进程中访问单例。所以我写了一个服务器来帮助共享实例。

但有些问题,似乎客户端获得了自己的单例版本,而不是原始实例。

最小的例子来自两个项目。这是客户端:

Program

using IPCServer;
using System;
using System.Diagnostics;
using System.ServiceModel;
using System.Threading;

namespace IPCEventTest

    class IPCClient
    
        static void Main(string[] args)
        
            Process ipcserver = Process.Start("IPCServer.exe");
            Thread.Sleep(2000);
            Console.WriteLine($"DateTime.Now.ToUniversalTime() Main Start Connect");
            Module module = Connect("WellKnownName"); //this name is used by the host
            Console.WriteLine($"DateTime.Now.ToUniversalTime() Main End Connect");
            Console.WriteLine($"DateTime.Now.ToUniversalTime() Main Start Raise");
            module.RaiseEvent(); //raise event should raise within the server process
            Console.WriteLine($"DateTime.Now.ToUniversalTime() Main End Raise");
            while (true) ;
        
        public static Module Connect(string id)
        
            Console.WriteLine("Start Connect");
            ChannelFactory<IModuleServer> pipeFactory =
                  new ChannelFactory<IModuleServer>(
                    new NetNamedPipeBinding(),
                    new EndpointAddress($"net.pipe://localhost/id")
                    );
            IModuleServer serverProxy = pipeFactory.CreateChannel();
            Module ret = serverProxy.GetModule();
            Console.WriteLine("End Connect");
            return ret;
        
    

以下文件设置主机:

Program

using System;

namespace IPCServer

    class Program
    
        static HOST host;

        static void Main(string[] args)
        
            host = new HOST("WellKnownName");
            Module.Instance.myevent += Instance_myevent;
            Console.WriteLine($"DateTime.Now.ToUniversalTime() Server Subscribed to Module.Instance.id");
            while (true) ;
        

        private static void Instance_myevent(object sender, EventArgs e)
        
            Console.WriteLine($"DateTime.Now.ToUniversalTime() Server Event Fired from (sender as Module).id");
        
    

Module

using System;
using System.Linq;

namespace IPCServer

    public class Module
    
        public static Module Instance  get;  = new Module();
        public event EventHandler myevent = delegate  ;
        public string id;

        private Module()
        
            var guid4 = Guid.NewGuid().ToString().Take(4);
            id = new String(guid4.ToArray());
            Console.WriteLine($"Module Constructor id");
            myevent += Module_myevent;
        

        private void Module_myevent(object sender, EventArgs e)
        
            Console.WriteLine($"Module Listener (sender as Module).id");
        

        public void RaiseEvent()
        
            Console.WriteLine($"Module Start Raise id");
            myevent(this, EventArgs.Empty);
            Console.WriteLine($"Module End Raise id");
        
    

Host

using System;
using System.ServiceModel;

namespace IPCServer

    internal class HOST
    
        ServiceHost host;

        internal HOST(string id)
        
            host = new ServiceHost(typeof(ModuleServer), new Uri[]  new Uri("net.pipe://localhost") );
            host.AddServiceEndpoint(typeof(IModuleServer), new NetNamedPipeBinding(), id);
            host.Open();
            Console.WriteLine($"DateTime.Now.ToUniversalTime() Host Opened");
        

        ~HOST()
        
            if (host.State == CommunicationState.Opened)
            
                host.Close();
            
            host = null;
            Console.WriteLine($"DateTime.Now.ToUniversalTime() Host Destructed");
        
    

    [ServiceContract]
    public interface IModuleServer
    
        [OperationContract]
        Module GetModule();
    

    public class ModuleServer : IModuleServer
    
        public Module GetModule()
        
            Console.WriteLine($"DateTime.Now.ToUniversalTime() ModuleServer start GetModule");
            Module ret = Module.Instance;
            Console.WriteLine($"DateTime.Now.ToUniversalTime() ModuleServer end GetModule");
            return ret;
        
    

示例运行,这是我系统上的输出:

为什么我没有从服务器进程中获取我的 Singleton。 为什么我的事件没有在服务器中引发。

编辑:服务器打开主机并订阅单例。客户端连接后,它通过成员函数引发事件。事件有两个订阅者,一个在构造函数中,一个在服务器端。仅处理 Module 内部订阅 - 服务器端没有事件处理 - 服务器端没有触发任何事件。模块侦听器被触发,但不在主机进程内。此事件在客户端处理。

【问题讨论】:

哪些事件没有被引发(我看了几次) @BugFinder 我编辑以澄清这一点 - 你是对的,输出列出了处理事件的侦听器。但这不是主机订阅,也不在主机进程内。 【参考方案1】:

为什么我没有从服务器进程中获取我的单例

命名管道使用序列化将对象从服务器传递到客户端。这意味着客户端必须重新运行构造函数并复制现有属性。public string id; 是一个字段,因此不会被“复制”,因此会保留构造函数设置的随机值。这就是为什么“相同”对象有不同的 ID。

要解决这个问题,您可以将其更改为:

[DataContract]
public class Module

    [DataMember]
    public string id get; set;


为什么我的事件没有在服务器中引发。

这不是 WCF 命名管道的工作方式,因为您在客户端只有一个重复版本。我建议你阅读Duplex Channel

【讨论】:

谢谢-怀疑答案可能是我对命名管道完全错误。

以上是关于通过命名管道访问单例会创建第二个单例。未在主机端触发的事件的主要内容,如果未能解决你的问题,请参考以下文章

为啥在第一个连接结束后创建第二个连接会导致管道异常?

javascript-单例模式

什么是单例?

斯卡拉玩!带有 2 个单选按钮的表单验证

单例模式,这个面试题真那么简单吗?

2个Windows服务C#之间的管道