3

我有一个 WCF 服务,托管在 IIS 中。该服务由具有接口类型作为参数或返回类型的通用接口定义,因此我们使用 ServiceKnownType 属性来定义所述接口在运行时的可用实现。

这一切似乎都运行良好,但是有时我们会看到对这些服务的所有请求都失败并出现 CommunicationException;“尝试序列化参数http://tempuri.org/时出错:arg。InnerException消息是 'Type ' MyNamespace.SomeInterfaceImplementation ',数据合同名称为 'SomeInterfaceImplementation:http://schemas.datacontract.org/2004 /07/MyNamespace' 不是预期的。将任何静态未知的类型添加到已知类型列表中 - 例如,通过使用 KnownTypeAttribute 属性或将它们添加到传递给 DataContractSerializer 的已知类型列表中。'。请参阅 InnerException更多细节。

我无法可靠地重现此错误,但它经常在服务运行一段时间后出现(例如,在周末)。我最初推测这是由于 IIS 应用程序池回收而发生的,但是手动回收或按小间隔(例如 2 分钟)安排的回收无法重现该问题。

通过从“ServiceKnownTypes”的提供者中排除“ MyNamespace.MyType ”,我能够可靠地重现异常,但这并不能真正告诉我为什么这首先会间歇性发生。

以下代码段显示了服务声明。它是针对不同实现类型的通用服务. 请注意,产生 CommunicationException 的是操作参数“ arg ”。

[ServiceKnownType("GetKnownTypes", typeof(KnownTypesCache))]
[ServiceContract]
public interface IMyService<T>
    where T : class, IMyServiceTypeInterface
{
    [OperationContract]
    [TransactionFlow(TransactionFlowOption.Allowed)]
    MyReturnType HandleRequest(T element, ISomeInterfaceArgumentType arg);
}

现在,为ISomeInterfaceArgumentType提供已知类型的 KnownTypesCache 的实现是这样的;

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 构造函数,因此缓存在实例的生命周期内将是空的......?

4

1 回答 1

2

我剩下的最后一个想法是,在将所有程序集加载到 AppDomain 之前,可能会调用静态 KnownTypesCache 构造函数

有了这个,您可能正在回答自己的问题。

AppDomain.GetAssemblies()只返回那些已经加载到 AppDomain 执行上下文中的程序集。在对创建新 AppDomain 的 IIS 工作进程进行任何回收之后,您在第一次使用 KnownTypesCache 类型和加载包含您的 IKnownTypesPublisher 类型之一的任何程序集之间存在潜在的竞争条件。间歇性的、难以重现的错误通常是由竞争条件引起的。

为了使您的设计正常工作,您需要以某种方式确保服务实现总是在调用 KnownTypesCache 方法之前加载所有包含您已知类型的程序集。

于 2012-07-23T09:15:45.553 回答