在不同 AppDomain 上的序列化分类之间同步属性/字段

Posted

技术标签:

【中文标题】在不同 AppDomain 上的序列化分类之间同步属性/字段【英文标题】:Synchronizing properties / fields between serialized classed on different AppDomains 【发布时间】:2013-06-24 04:20:21 【问题描述】:

情况就是这样——我有一个派生自抽象基类的类(因此不能从 MarshalByRefObject 派生并用作代理对象),并且它具有正确的状态很重要,因为它正在处理与流并做一些重要的事情。

它被标记为 Serializable 因为我需要将它传递给另一个 AppDomain 并让该 appdomain 定期调用 .Post(string) 方法。

我对 appdomains 一无所知,但我看到了一些奇怪的事情,我假设该类实际上在第二个 AppDomain 中使用相同的字段和属性重新实例化,然后我最终得到了多个实例类的。

这是一个问题,因为当第二个 AppDomain 调用 .Post() 时,它会修改流和它自己的对象状态,但主 appdomain 中的类中包含的私有变量没有更新 - 所以有主应用程序域中的一个损坏状态,一旦主应用程序域在其自己的类版本上调用.Post,我就会得到一个损坏的流!

我也不能使用包装类,因为我需要传递作为其抽象基类的对象,而第二个appdomain不知道派生类的类型是什么,只知道调用@ 987654324@是基类中定义的抽象方法。

有什么方法可以将第二个 appdomain 对类所做的更改更新回命令上的主 appdomain 字段,以便我可以“同步”字段的状态?

【问题讨论】:

【参考方案1】:

我认为您可以使用 MarshalByRefObject 包装类来完成这项工作。

考虑以下示例:

using System;
using System.IO;
using System.Reflection;

namespace ConsoleApplication13

    class Program
    
        static void Main(string[] args)
        
            AppDomain appDomain = AppDomain.CreateDomain("foo");

            FooFactory fooFactory = (FooFactory)appDomain.CreateInstanceFromAndUnwrap(Assembly.GetEntryAssembly().Location, typeof(FooFactory).FullName);

            IFoo fooFromOtherDomain = fooFactory.CreateMeAFoo();

            string message = "Hello World!";

            Console.WriteLine("Data = 0 on AppDomain ID = 1", message, AppDomain.CurrentDomain.Id);

            byte[] buffer = System.Text.Encoding.UTF8.GetBytes(message);
            fooFromOtherDomain.Post(buffer);
        
    

    public interface IFoo
    
        void Post(byte[] data);
    

    public abstract class FooBase
    

    /// <summary>
    /// This class represents your class that can't be marshaled by ref...
    /// </summary>
    public class Foo : FooBase, IFoo, IDisposable
    
        private MemoryStream _buffer;

        public Foo()
        
            this._buffer = new MemoryStream();
        

        public void Post(byte[] data)
        
            if (data == null)
                throw new ArgumentNullException("data");

            this._buffer.Seek(0, SeekOrigin.End);
            this._buffer.Write(data, 0, data.Length);

            OnNewData();
        

        private void OnNewData()
        
            string dataString = System.Text.Encoding.UTF8.GetString(this._buffer.ToArray());
            Console.WriteLine("Data = 0 on AppDomain ID = 1", dataString, AppDomain.CurrentDomain.Id);
        

        public void Dispose()
        
            this._buffer.Close();
        
    

    /// <summary>
    /// Wraps the non-remote Foo class and makes it remotely accessible.
    /// </summary>
    public class FooWrapper : MarshalByRefObject, IFoo
    
        private IFoo _innerFoo;

        public FooWrapper(IFoo innerFoo)
        
            this._innerFoo = innerFoo;
        

        public void Post(byte[] data)
        
            this._innerFoo.Post(data);
        
    

    /// <summary>
    /// For demo purposes to get an instance of IFoo from the other domain.
    /// </summary>
    public class FooFactory : MarshalByRefObject
    
        public IFoo CreateMeAFoo()
        
            Foo foo = new Foo();
            FooWrapper fooWrapper =new FooWrapper(foo);

            return fooWrapper;
        
    

不要太在意 FooFactory 类。这只是为了方便远程创建展示您的场景的对象。

基本上为您的远程类定义一个接口,例如IFoo

public interface IFoo

    void Post(byte[] data);

创建一个派生自MarshalByRefObject并实现IFooFoo包装类

/// <summary>
/// Wraps the non-remote Foo class and makes it remotely accessible.
/// </summary>
public class FooWrapper : MarshalByRefObject, IFoo

    private IFoo _innerFoo;

    public FooWrapper(IFoo innerFoo)
    
        this._innerFoo = innerFoo;
    

    public void Post(byte[] data)
    
        this._innerFoo.Post(data);
    

将您的 IFoo 实现传递给 FooWrapper

Foo foo = new Foo();
FooWrapper fooWrapper = new FooWrapper(foo);

然后将FooWrapper返回到另一个域并像往常一样调用IFoo方法,如Post

这个程序的执行输出:

【讨论】:

这可以工作 - 但需要做很多工作,因为现在一切都被转换回抽象类的类型。为了做到这一点,我需要定义一个新接口并使用它而不是基类 @caesay- 只有您需要跨域访问的公共方法和属性才需要在接口中。 [编辑-我明白你的意思,你已经在你的调用中引用了基类,重构是很多工作,你在使用 Visual Studio 吗?它可以帮助重构您的代码。无论如何,跨 AppDomains 传递基类可能不是最好的事情。不如使用接口灵活] 或者,您是否控制抽象基类?你能让它派生自 MarshalByRefObject 吗? @caesay,如果你不能重构你的代码来传递一个接口而不是你的基类,恐怕你的选择是有限的,但你可以使用某种事件中继,当一个域中的值发生变化时,该更改将转发给其他域中的同伴并在内部更新值。虽然这看起来很脆弱而且更复杂,但如果你不能重构,那么你需要一个共享的远程对象为你来回代理更改...... 如果它包含一个派生自 MarshalByRefObject 的内部类,它会在第二个 appdomain 中重新实例化还是被代理?可以在属性设置器上调用它,但我不确定如何让它更新其他 appdomain。 @caesay - 不,不幸的是,这行不通。当对象不是 MarshalByRefObject 派生时,它使用序列化,这本质上是“按值”,这意味着当实例跨域传递时,序列化“发送”域中的对象,然后(反序列化)创建类型的新实例“接收”域并继续设置每个属性。所以即使是内部引用也会以这种方式序列化。传递“by ref”的唯一方法是从 MarshalByRefObject 派生。我真的认为提取公共接口并进行重构符合您的最大利益。

以上是关于在不同 AppDomain 上的序列化分类之间同步属性/字段的主要内容,如果未能解决你的问题,请参考以下文章

如何在两个 .NET AppDomain 之间传递未知类型?

将派生类型序列化为 AppDomain 上的基本类型

从不同的 AppDomain 调用 SignalR 方法

为啥实体框架在不同的 AppDomain 中运行时会明显变慢?

远程服务器上的 CoreData 在不同客户端(应用程序)之间同步

数据同步