为什么在调用ServiceHost.Open之前实例化XmlSerializer会产生内存和句柄泄漏

Sta*_*eXV 8 .net c# wcf windows-services xml-serialization

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

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

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

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

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

Program.cs中

WindowsService winSvc = new WindowsService();
ServiceBase[] servicesToRun = new ServiceBase[]{winSvc};                    
ServiceBase.Run(servicesToRun);
Run Code Online (Sandbox Code Playgroud)

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

DemoService.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Client.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();  
        }
    )
}
Run Code Online (Sandbox Code Playgroud)

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

请注意,我没有兴趣找到一种方法来避免泄漏(我已经知道如何做到这一点),但在解释中(即为什么会发生这种情况).

atl*_*ste 7

我认为一些内部运作这个问题是正义的.我从头脑中做到这一点,因为我前段时间遇到了这个问题,我花了一天的时间来追踪,包括大量使用Reflector和ANTS Memory Profiler(在我以前的公司)......这里去:

XML Serializer在内部做的是使用System.Reflection.Emit创建一个类(让我们称之为'A'),接受传递给它的类型.构建这样的类相对来说花费了大量时间,并且可以重复使用,因为类型不会改变.因此,构造的类型存储在字典中,例如,它以一些字典结束.

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

XMLSerializer第一次使用该类型时,此过程同时发生您传递的类型及其所需的所有类型(例如,需要序列化的所有字段和属性).

关于泄漏......当你调用ChannelFactory时,如果它还不存在,它会构造序列化器.为此,它检查序列化程序是否已存在于Dictionary中,如果不存在,则通过创建ISomeSerializerType的实例来创建一个.

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

希望这能解释.


ta.*_*.is 6

XmlSerializer文档说明了这一点:

为了提高性能,XML序列化基础结构动态生成程序集以序列化和反序列化指定的类型.基础结构查找并重用这些程序集.仅当使用以下构造函数时,才会出现此问题:

XmlSerializer.XmlSerializer(类型)

XmlSerializer.XmlSerializer(Type,String)

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

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