65.9K
CodeProject 正在变化。 阅读更多。
Home

应用程序域间通信

starIconstarIconstarIconstarIconstarIcon

5.00/5 (1投票)

2013年7月8日

CPOL

3分钟阅读

viewsIcon

21963

使用 AppDomain 加载另一个 .NET 程序集版本。

引言

有时在我们的应用程序中,我们希望在运行时动态加载程序集。 我们可以做的是使用 Assembly.Load 方法。 但有些情况下可能会导致兼容性问题。 例如,您的主应用程序使用一个版本的 UnityContainer,而您要动态加载的程序集使用另一个版本的 UnityContainer。 这将导致无法加载程序集。

解决方案

有几种解决方案

  1. 在另一个进程上托管的 WCF 服务:因为不同的进程允许您加载不同版本的程序集。
  2. 强命名程序集:解决此问题的官方 .NET 技术。
  3. 新的 AppDomain:因为不同的 AppDomain 可以隔离类型和程序集。

解决方案 1 看起来有点过头,对于一个简单的情况。

对于解决方案 2,需要对所有程序集进行签名,并在 GAC 中注册它们。 在我们的例子中,我们希望避免它引入的开销。

解决方案 3 是一种轻量级方法。 我们将在本文中讨论如何实现它。

var domain = AppDomain.CreateDomain("TestService");
var tester = (ITester)domain.CreateInstanceAndUnwrap(
    typeof(Tester).Assembly.FullName,
    typeof(Tester).FullName);

您还可以通过以下方式指定默认的程序集搜索路径

var setup = new AppDomainSetup { ApplicationBase = GetPath() };
var domain = AppDomain.CreateDomain("TestService", null, setup);
var tester = (ITester)domain.CreateInstanceAndUnwrap(
   typeof(Tester).Assembly.FullName,
   typeof(Tester).FullName);

进程内远程处理

为了在另一个 AppDomain 中调用类型实例,我们需要让该类型继承自 MarshalByRefObject

public class Tester : MarshalByRefObject, ITester

.NET 将自动为您创建一个远程处理代理。 因此您可以像往常一样使用该类型。 但是在远程域中实例化的类型依赖于客户端的域垃圾收集器。 如果客户端域崩溃,远程对象将不会被释放。 因此 .NET 提供了一种机制来避免这种情况。 远程对象将在五分钟未使用后自毁。 由于在我们的例子中,客户端在默认的 AppDomain 中运行,当它结束时,整个进程也将结束。 因此,我们可以使用以下代码禁用默认的自毁行为。 在类中,我们可以重写 InitializeLifetimeService 并返回 null

public override object InitializeLifetimeService()
{
    return null;
}

对于引用类型,我们可以从 MarshalByRefObject 继承。 但是对于那些值类型呢? 实际上,大多数值类型默认都是可远程处理的。 例如 intstringenum 等。 但是对于结构体类型,我们需要将其标记为 Serializable

[Serializable]
public struct Result
{
    public string Outcome;
} 

此外,委托也是可序列化的。 实际上,我们也可以用 Serializable 标记引用类型。 它也将支持远程处理操作。 但是运行时语义是不同的。 使用 MarshalByRefObject,客户端代码将通过代理调用远程对象,并且远程对象在新的 AppDomain 中运行。 但是,如果将 Serializable 应用于所有类型,则序列化过程将在客户端 AppDomain 中创建一个新实例。 这意味着所有后续调用都将在客户端 AppDomain 中执行。

异常处理

为了避免将所有类型标记为 Serializable,我们选择 Marshalling 方法。 对于值类型,将其标记为 Serializable。 它可以让 AppDomain 相互通信。 在抛出异常的情况下,我们可能需要一些特殊处理来使异常 Serializable。 对于系统定义的异常,.NET 已经处理了这一点。 它们不需要被序列化。 但是对于用户定义的异常,我们需要处理它们:如果异常没有自定义属性,我们需要做的是将它们标记为 Serializable。 例如,假设我们定义了一个名为 RetryableException 的异常。

[Serializable]
[ComVisible(true)]
public class RetryableException : Exception
{
    public RetryableException(){}

    public RetryableException(string message) : base(message) { }

    public RetryableException(string message, Exception ex) : base(message, ex)  { }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    // Constructor should be protected for unsealed classes, private for sealed classes.
    // (The Serializer invokes this constructor through reflection, so it can be private)
    protected RetryableException(SerializationInfo info,
        StreamingContext context)
        : base(info, context)
    {
    }
}

如果异常具有自定义属性,我们需要在调用序列化构造函数中添加一些实现,并重写 GetObjectData(SerializationInfo info, StreamingContext context),该方法在 ISerializable 接口中定义。

[Serializable]
[ComVisible(true)]
public class RetryableException : Exception
{
    public Action RetryTask { get; set; }
    public Exception Exception { get; set; }
    public string Name { get; set; }

    public RetryableException(){}

    public RetryableException(string message) : base(message) { }

    public RetryableException(string message, Exception ex) : base(message, ex)  { }

    public RetryableException(Action retry, Exception e, string name)
    {
        RetryTask = retry;
        Exception = e;
        Name = name;
    }

    [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
    // Constructor should be protected for unsealed classes, private for sealed classes.
    // (The Serializer invokes this constructor through reflection, so it can be private)
    protected RetryableException(SerializationInfo info,
        StreamingContext context)
        : base(info, context)
    {
        Exception = (Exception)info.GetValue("Exception", typeof(Exception));
        RetryTask = (Action)info.GetValue("RetryTask", typeof(Action));
        Name = info.GetString("Name");
    }

    [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
    public override void GetObjectData(SerializationInfo info, StreamingContext context)
    {
        if (info == null)
        {
            throw new ArgumentNullException("info");
        }

        info.AddValue("Exception", Exception, typeof(Exception));
        info.AddValue("RetryTask", RetryTask, typeof(Action));
        info.AddValue("Name", Name);

        // MUST call through to the base class to let it save its own state
        base.GetObjectData(info, context);
    }
}

[Serializable] 和 ISerializable

请注意,[Serializable]ISerializable 不可继承。 这意味着无论定义的类从哪个类继承,如果您希望它可序列化,您仍然需要将其标记为 Serializable。 即使它实现了 ISerializable 接口也是如此。

关注点

在本文中,我们讨论了如何使用 AppDomain 加载已经在默认 AppDomain 中加载的另一个 .NET 程序集版本。 并且我们还讨论了在支持远程处理时应用 MarshalByRefObject[Serializable] 之间的区别。

© . All rights reserved.