.NET AppDomain 隔离技术与异常处理

.NET开发中,经常会遇到需要加载不同版本的同一个程序集的情况。例如,主应用程序使用了一个版本的UnityContainer,而动态加载的程序集使用了另一个版本的UnityContainer,这将导致程序集无法加载。为了解决这个问题,可以使用多种方法。

一种方法是使用WCF服务,它托管在另一个进程中,因为不同的进程允许加载不同版本的程序集。另一种方法是使用强命名程序集,这是.NET官方解决此类问题的技术。第三种方法是使用新的AppDomain,因为不同的AppDomain可以隔离类型和程序集。

第一种解决方案对于简单案例来说可能有点过于复杂。第二种解决方案需要对所有程序集进行签名并注册到GAC中,希望避免它引入的开销。第三种解决方案是一种轻量级的方法。本文将讨论如何实现它。

在C#中,可以使用以下代码创建一个新的AppDomain:

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。.NET将自动为创建一个远程代理。因此,可以像使用普通类型一样使用它。但是,在远程域中实例化的类型依赖于客户端域的垃圾回收器。如果客户端域崩溃,远程对象将不会被释放。因此,.NET提供了一种机制来避免这种情况。

远程对象将在五分钟未使用后自动销毁。在案例中,客户端在默认AppDomain中运行,当它结束时,整个进程也将结束。因此,可以使用以下代码禁用默认的自毁行为:

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。这可以工作,让AppDomains相互通信。在抛出异常的情况下,可能需要对异常进行特殊处理,使其可序列化。对于系统定义的异常,.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) { } }

如果异常具有自定义属性,需要在序列化调用的构造函数中添加一些实现,并覆盖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)] 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); base.GetObjectData(info, context); } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485