应用程序域间通信
使用 AppDomain 加载另一个 .NET 程序集版本。
引言
有时在我们的应用程序中,我们希望在运行时动态加载程序集。 我们可以做的是使用 Assembly.Load
方法。 但有些情况下可能会导致兼容性问题。 例如,您的主应用程序使用一个版本的 UnityContainer,而您要动态加载的程序集使用另一个版本的 UnityContainer。 这将导致无法加载程序集。
解决方案
有几种解决方案
- 在另一个进程上托管的 WCF 服务:因为不同的进程允许您加载不同版本的程序集。
- 强命名程序集:解决此问题的官方 .NET 技术。
- 新的 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
继承。 但是对于那些值类型呢? 实际上,大多数值类型默认都是可远程处理的。 例如 int
、string
、enum
等。 但是对于结构体类型,我们需要将其标记为 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]
之间的区别。