8

在 .NET/WCF/Windows 服务中寻找内存和句柄泄漏时,我注意到我无法解释的奇怪行为。这里是设置和分辨率。我正在寻找的是对观察到的行为的解释。

我安装了一个 Windows 服务。
我启动了服务。
我使用事务性 WCF 调用调用了一个简单的方法(每次调用新通道 - 无缓存)。
对于每个调用,大约 2 个句柄保留在内存中。

如果以下项目适用,则可以观察到这一点:

  1. 它是一个 Windows 服务;不要将其作为控制台应用程序运行。
  2. 使用事务(仅单独的进程或机器测试)调用 WCF 方法。
  3. ServiceBase.Run(servicesToRun);在使用某种类型调用实例化 XmlSerializer 之前。
  4. 该类型是自定义类型。它不会与new XmlSerializer(typeof(string))或 new一起发生XmlSerializer(typeof(XmlDocument))。无需调用序列化。如果自定义类型只有一个字符串作为属性就足够了(任何地方都没有句柄!)
  5. 使用即 SGen.exe 创建静态 XmlSerialization.dll 不会产生此问题。

我的代码已经包含修复: 在 OnStart() 中最早
使用 XmlSerializer :

程序.cs

WindowsService winSvc = new WindowsService();
ServiceBase[] servicesToRun = new ServiceBase[]{winSvc};                    
ServiceBase.Run(servicesToRun);

WindowsService.cs

internal sealed class WindowsService : ServiceBase
{
    private ServiceHost wcfServiceHost = null;

    internal WindowsService()
    {
        AutoLog = true;
        CanStop = true;
        CanShutdown = true;
        CanPauseAndContinue = false;
    }

    internal void StartWcfService()
    {
        wcfServiceHost = new ServiceHost(typeof(DemoService));
        wcfServiceHost.Open();
    }

    protected override void Dispose(bool disposing)
    {
        if (wcfServiceHost != null)
        {
            wcfServiceHost.Close();
        }

        base.Dispose(disposing);
    }

    protected override void OnStart(string[] args)
    {
        new XmlSerializer(typeof(MyType));

        StartWcfService();
    }
}

演示服务.cs

[ServiceBehavior
    (
        InstanceContextMode = InstanceContextMode.PerSession,
        TransactionAutoCompleteOnSessionClose = false,
        IncludeExceptionDetailInFaults = true
    )
]
public sealed class DemoService : IDemoService
{           
    [TransactionFlow(TransactionFlowOption.Allowed)]
    [OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]
    public int Add(int a, int b)
    {
        return a + b;
    }
}

客户端.cs

IChannelFactory<IDemoService> channelFactory = new ChannelFactory<IDemoService>("defaultClientConfiguration");
IDisposable channel = null;
for (int index = 0; index < 5000; index++)
{
    using
    (
        channel = (IDisposable)channelFactory.CreateChannel(new EndpointAddress("net.tcp://localhost:23456/DemoService")))
        {                       
        IDemoService demoService = (IDemoService)channel;
        using (TransactionScope tx = new TransactionScope(TransactionScopeOption.RequiresNew))
        {
            demoService.Add(3, 9);
            tx.Complete();  
        }
    )
}

有人可以解释这种行为吗?

请注意,我对找到避免泄漏的方法不感兴趣(我已经知道如何做到这一点),而是在解释中(即为什么会发生)。

4

2 回答 2

7

我认为一些内部运作可以解决这个问题。我从脑后开始这样做,因为我前段时间也遇到了这个问题,我花了一天的时间来回溯,包括广泛使用 Reflector 和 ANTS 内存分析器(在我以前的公司)......在这里去:

XML Serializer 在内部所做的是它使用 System.Reflection.Emit 创建一个类(我们称之为“A”),该类接受您传递给它的类型。相对而言,构建这样一个类需要花费大量时间,并且由于类型不会改变,因此可以重用。因此,构造的类型存储在字典中,例如它以一些 Dictionary 结尾。

对于已知(基本)类型,序列化程序代码是固定的,例如,无论您重新启动应用程序多少次,字符串的序列化都不会改变。请注意与“A”的区别,即序列化工厂不知道的任何类型,直到它第一次传递给 XMLSerializer。

XMLSerializer 第一次使用该类型时,这个过程会发生在您传递的类型和它需要的所有类型(例如所有需要序列化的字段和属性)上。

关于泄漏...当您调用 ChannelFactory 时,如果它尚不存在,它会构造序列化程序。为此,它检查序列化程序是否已存在于字典中,如果不存在,则通过创建 ISomeSerializerType 实例来创建序列化程序。

由于某些愚蠢的原因,工厂中存在一个错误,它构造了一个新的序列化程序而不将其存储在字典中。一旦构造完成,你最终会得到一个新类型——它显示为泄漏(记住:类型永远不能被卸载)——即使对象被正确处理。当您首先使用 XMLSerializer 或创建静态类时,它会正确使用 Dictionary 缓存,这意味着它不会泄漏。所以你有它,这是一个错误。我曾经可以访问 ANTS Memory Profiler,它很好地展示了这一点。

希望这能解释。

于 2013-01-09T19:37:47.907 回答
6

XmlSerializer 文档是这样说的:

为了提高性能,XML 序列化基础结构动态生成程序集以序列化和反序列化指定类型。基础结构查找并重用这些程序集。此行为仅在使用以下构造函数时发生:

XmlSerializer.XmlSerializer(类型)

XmlSerializer.XmlSerializer(类型,字符串)

如果您使用任何其他构造函数,则会生成同一程序集的多个版本并且永远不会卸载,这会导致内存泄漏和性能下降。最简单的解决方案是使用前面提到的两个构造函数之一。否则,您必须将程序集缓存在 Hashtable 中,如下例所示。

http://msdn.microsoft.com/en-us/library/system.xml.serialization.xmlserializer.aspx

于 2012-10-26T12:28:00.410 回答