【问题标题】:Object Sharing between Applications?应用程序之间的对象共享?
【发布时间】:2015-04-04 18:44:57
【问题描述】:

假设我有一个每秒更新 1000 多次的大型数据数组。
另一个应用程序想要在短时间内访问和读取数组。两个应用程序都在同一台机器上。

我曾尝试使用 WCF 进行进程间通信,但是每秒数千次序列化和发送整个数组(或大型对象)在性能方面是不可行的。
有没有办法在 c# 中直接访问来自不同应用程序的对象?

【问题讨论】:

  • 您在做什么以每秒更新 1000 次以上的数据数组?这听起来像是一个架构问题。您真的需要实时发送每个更新吗?你可以排队更新吗?您可以批量更新,以便在“消息”中发送多个更新吗?
  • @Scott Chamberlain Gaming/Simulations,他们需要实时处理。是的,也许将组件分离并在不同的应用程序中运行它们是一种有缺陷的方法。
  • 如果是游戏,我会重新评估您从单独的应用程序中获得的净收益,以及这些收益是否值得这种缓慢的成本。如果是模拟,我会尝试批量或排队数据以更大的块发送,这样每次更新的开销成本可以摊销。
  • @pm100 sharedmemory 听起来不错,但它只能共享缓冲区和基元数组,而不是对象,而不是对象数组 - 它们必须被序列化,然后写入该共享缓冲区,与管道相同.

标签: c# object sharing interprocess


【解决方案1】:

您可以使用一些 IPC 技术,尽管早期的 WCF 在今天仍然适用。

管道

管道就是这样一种技术。它是二进制的,在内核模式下运行并且非常快!虽然它是相当低级的,并且提供对“对象”的访问权限。

.NET 远程处理

.NET Remoting 可以访问对象,但可能不如管道快。

管道和 .NET 远程处理都比序列化技术 WCF 更快,后者将事物转换为详细的 XML/SOAP。

COM

COM 是 IPC 的二进制协议。 COM 是一种客户端服务器模型,客户端从 COM 或 OLE 服务器请求数据。 COM 的美妙之处在于您可以直接访问服务器中的对象——它们没有被序列化。例如请求 SAFEARRAY 中的元素。

SAFEARRAY 是由类型安全数据组成的任意维度的自动化安全结构。幸运的是 .NET 会为我们隐藏 SAFEARRAY gobble-de-gook。

在我的示例中,我创建了一个 Manager 类,它将公开数组。为了获得Manager,我使用了工厂模式,因此Manager 本质上是一个单例。

您应该按如下方式布置您的项目:

  • MyComLib.Contracts.dll - 包含所有接口
  • MyComLib.dll - 包含FactoryManager 的实现

首先是合同:

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IArrayItem
{
    #region Properties

    string Name { get; set; }

    int Whatsit { get; set; }

    #endregion
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IFactory 
{
    #region Methods

    IManager CreateManager();

    #endregion
}

[ComVisible(true)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IManager
{
    #region Properties

    IArrayItem[] Array { get; }

    #endregion
}

public static class MyComLibConstants
{
    public const string FactoryProgId = "MickyD.MyComLib.Factory.1";
    
}

现在是工厂模式:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof (IFactory))]
[Guid("...")]
[ProgId(MyComLibConstants.FactoryProgId)]
public class Factory : MarshalByRefObject, IFactory
{
    #region IFactory Members

    /// <summary>
    /// Creates the manager.
    /// </summary>
    /// <returns></returns>
    public IManager CreateManager()
    {
        return Manager.Instance;
    }


    #endregion
}

经理:

[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof (IManager))]
[Guid("...")]
internal sealed class Manager : MarshalByRefObject, IManager
{
    private static Manager _instance;

    #region Constructor

    /// <summary>
    /// Prevents a default instance of the <see cref="Manager"/> class from being created.
    /// </summary>
    private Manager()
    {
        const int n = 5000;
        Array = new IArrayItem[n];
        for (int i = 0; i < n; i++)
        {
            Array[i]=new ArrayItem();
        }
    }

    #endregion

    #region Properties

    
    /// <summary>
    /// Gets the instance.
    /// </summary>
    /// <value>
    /// The instance.
    /// </value>
    public static IManager Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Manager();
            }
            return _instance;
        }
    }

    #endregion

    #region IManager Members

    /// <summary>
    /// Gets the array.
    /// </summary>
    /// <value>
    /// The array.
    /// </value>
    public IArrayItem[] Array { get; private set; }

    #endregion
}

一个测试应用。这应该引用 MyComLib.Contracts.dll不引用 MyComLib.dll

class Program
{
    static void Main(string[] args)
    {
        var type = Type.GetTypeFromProgID(MyComLibConstants.FactoryProgId);
        var factory = Activator.CreateInstance(type) as IFactory;
        var manager = factory.CreateManager();
        var x = manager.Array[500].Whasit;
    }
}

最后一步是将此进程内 COM 服务器更改为进程外 COM 服务器,以便多个进程各自共享相同的Manager 和不要创建自己的单身人士。换句话说,一个跨越进程的单例。当Manager 运行时,它本质上是在它自己的进程空间中,与所有其他客户端进程分开。

为此,您需要配置一个 COM 代理,详细说明 here

文件映射/共享内存

最后,文件映射允许您像处理进程地址空间中的一大块内存一样操作文件。没有繁琐的文件搜索;读/写操作。只需抓住指向内存块的指针并开始读/写。系统会完成剩下的工作。

MSDN:

您可以使用文件映射的特殊情况在进程之间提供命名共享内存。如果在创建文件映射对象时指定系统交换文件,则文件映射对象被视为共享内存块。其他进程可以通过打开同一个文件映射对象来访问同一个内存块。 Tell me more

遗憾的是,它仍然需要您首先写入数据,并且要使其最有效,您需要更改应用程序以将内存块视为事实来源,而不是内存中的数组。否则你会一直在序列化。

但是,通过交换文件共享内存在技术上确实允许您消除客户端-服务器应用程序之间的任何序列化-反序列化以及“堆上”的数据重复。虽然正如我所说,您可能需要调整您的应用程序以使用原始内存缓冲区而不是对象。

告诉我更多

注意:与普遍看法相反,.NET Remoting并非完全过时。它的一种当代用途是在同一进程中不同AppDomains 中的对象之间进行通信,您通常在plug-in systems 中执行此操作。

【讨论】:

  • WCF 已经使用了命名管道,但一切都被序列化了。我读过的 Net Remoting 也序列化了通信数据。
  • 我确实提到过。序列化并不总是相同的 - WCF 使用反射并且可以作为详细的 XML/SOAP 持续存在。在我的管道建议中,我完全是在说你 bypass WCF发送原始二进制数据 - 你在字节级别控制序列化。这将大大优于 WCF。祝你好运!
  • @brandon WCF 是一些连接(无论是命名管道、TCP 还是其他)的包装器,以使其更易于使用。该包装器需要花费一些开销。 Micky 的建议是剥离包装并使用原始结构,并进行您自己的自定义专业序列化,可以将其调整为尽可能快。
  • @user2481095 我希望共享内存是最快的,因为不涉及方法调用/vtable。只要不涉及网络层,Protobuf 就可能有用。它肯定会比 xml 更快。 :)
猜你喜欢
  • 1970-01-01
  • 2013-05-02
  • 2011-03-15
  • 2010-11-06
  • 2010-12-10
  • 1970-01-01
  • 2012-12-04
  • 1970-01-01
相关资源
最近更新 更多