我有一个WCF服务,托管在IIS中。该服务由一个通用接口定义,接口类型为参数或返回类型,因此我们使用ServiceKnownType属性来定义运行时所述接口的可用实现。
这一切似乎都很好,但有时,我们会看到所有对这些服务的请求都以CommunicationException失败;"尝试序列化参数时出错http://tempuri.org/:arg。InnerException消息为"Type"MyNamespace.SomeInterfaceImplementation",数据协定名称为"SomeInterfaceImplication:http://schemas.datacontract.org/2004/07/MyNamespace不应为"。将任何不静态已知的类型添加到已知类型列表中,例如,使用KnownTypeAttribute属性或将它们添加到传递给DataContractSerializer的已知类型列表。'。有关详细信息,请参阅InnerException"
我无法可靠地重现这个错误,但在服务运行一段时间后(例如,在周末),它会定期出现。我最初推测这是由于IIS应用程序池回收造成的,但手动回收或小间隔(如2分钟)的计划回收无法重现问题。
通过从"ServiceKnownTypes"的提供商中排除"MyNamespace.MyType",我能够可靠地再现异常,但这并不能真正告诉我为什么这种情况会间歇性发生。
下面的代码段显示了服务声明。它是用于的不同实现类型的通用服务。请注意,正是操作参数"arg"产生了CommunicationException。
[ServiceKnownType("GetKnownTypes", typeof(KnownTypesCache))]
[ServiceContract]
public interface IMyService<T>
where T : class, IMyServiceTypeInterface
{
[OperationContract]
[TransactionFlow(TransactionFlowOption.Allowed)]
MyReturnType HandleRequest(T element, ISomeInterfaceArgumentType arg);
}
现在,KnownTypesCache的实现是这样的,它为ISomeInterfaceArgumentType提供已知类型;
public static class KnownTypesCache
{
private static readonly List<Assembly> queriedAssemblies = new List<Assembly>();
private static readonly List<Type> knownTypes = new List<Type>();
static KnownTypesCache()
{
// get all available assemblies at this time
List<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().ToList();
// find all available known types publishers
IEnumerable<Type> knownTypesPublisherTypes = assemblies
.Where(a => !queriedAssemblies.Contains(a)) // exclude already queried assemblies to speed things up
.SelectMany(s => s.GetTypes())
.Where(p => typeof(IKnownTypesPublisher).IsAssignableFrom(p) && p.HasAttribute(typeof(KnownTypesPublisherAttribute)));
// add all known types
foreach (Type type in knownTypesPublisherTypes)
{
IKnownTypesPublisher publisher = (IKnownTypesPublisher)Activator.CreateInstance(type);
AddRange(publisher.GetKnownTypes());
}
}
// record the assemblies we've already loaded to avoid relookup
queriedAssemblies.AddRange(assemblies);
}
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
return knownTypes;
}
}
基本上,这个全局静态缓存查询AppDomain中所有加载的程序集,以查找"IKnownTypesPublisher"的实现者,后者反过来为每个程序集提供可用的序列化类型。因此,每个程序集有一个IKnownTypesPublisher,它只负责识别该程序集中的类型,而KnownTypesCache只是聚合所有这些类型,并在运行时返回到DataContractSerializer。
正如我所提到的,这种方法似乎在99%的情况下都很有效。然后,由于我无法识别的原因,它停止了工作,只能通过调用iisreset来解决。
我现在很困惑,我已经尝试了各种解决方案,但由于我只能等到周一的第一件事才能可靠地重现这个错误,这有点困难!
我剩下的最后一个想法是,在将所有程序集加载到AppDomain之前,可能会调用静态KnownTypesCache构造函数,因此缓存在实例的生命周期内将为空。。。?
我剩下的最后一个想法是静态KnownTypesCache构造函数可能在所有程序集加载到之前被调用AppDomain
有了这一点,你可能正在回答自己的问题。
AppDomain.GetAssemblies()
只返回那些已经加载到AppDomain的执行上下文中的程序集。在对IIS工作进程进行任何回收(创建新的AppDomain)后,在首次使用KnownTypesCache类型和加载任何包含IKnowntTypesPublisher类型之一的程序集之间可能存在竞争条件。间歇性的、难以繁殖的虫子,经常是由种族条件引起的。
为了使您的设计正常工作,您需要以某种方式确保服务实现始终加载包含已知类型的所有程序集,然后再调用KnownTypesCache方法。