当从另一个WCF服务中调用WCF服务时,出现WCF内存泄漏问题

4
我注意到一个内存泄漏问题,在一个WCF应用程序中,并已经用一个简单的程序复制了它。当在另一个WCF服务中调用WCF服务时,就会出现这个问题。
在以下示例中,我有两个服务A和B。当我调用服务A的DoWork方法时,它反过来调用服务B的DoWork方法。
在下面的示例中,每次我都创建一个新的ChannelFactory,使用它打开一个通道,调用DoWork方法,然后在结束时处理掉这个通道和工厂。这样,进程开始泄漏内存。
如果我设置其中任何一个或两个调用以重用相同的ChannelFactory(在示例中注释或取消注释标记的行),泄漏就停止了。
如果我每次调用服务A仍然创建一个新的ChannelFactory,但清空ServiceA的DoWork方法(因此不调用ServiceB),泄漏就不会发生。
我正在运行针对.NET 3.5目标的程序。奇怪的是,如果我切换到.NET 4、4.5或4.5.1,进程会更快地泄漏内存。
有人能理解为什么会发生这种情况,也许如何解决它(或至少绕过它)吗?
以下是示例代码:
using System;
using System.ServiceModel;

namespace memoryleak
{
    internal class Program
    {
        private static void Main()
        {
            using (var hostA = new ServiceHost(new ServiceA(), new Uri("net.pipe://localhost")))
            using (var hostB = new ServiceHost(new ServiceB(), new Uri("net.pipe://localhost")))
            {
                hostA.AddServiceEndpoint(typeof (ContractA), new NetNamedPipeBinding(), "test_service_a");
                hostA.Open();
                hostB.AddServiceEndpoint(typeof (ContractB), new NetNamedPipeBinding(), "test_service_b");
                hostB.Open();

                while(true)dowork();
            }
        }

        //CALLING SERVICE A

        //uncomment the following line to reuse the same ChannelFactory each time
        //private static readonly ChannelFactory<ContractA> pipeFactory=new ChannelFactory<ContractA>(new NetNamedPipeBinding(),new EndpointAddress("net.pipe://localhost/test_service_a"));

        private static void dowork()
        {
            //comment the following line to reuse the same ChannelFactory each time
            var pipeFactory = new ChannelFactory<ContractA>(new NetNamedPipeBinding(), new EndpointAddress("net.pipe://localhost/test_service_a"));

            ContractA provider = null;
            try
            {
                provider = pipeFactory.CreateChannel();
                provider.DoWork();
            }
            catch
            {
            }
            finally
            {
                CloseChannel(provider);

                //comment the following line to reuse the same ChannelFactory each time
                try { pipeFactory.Close(); }catch{pipeFactory.Abort();}
            }
        }

        private static void CloseChannel(ContractA provider)
        {
            try
            {
                if (provider == null)
                    return;
                try
                {
                    ((IClientChannel) provider).Close();
                }
                catch
                {
                    ((IClientChannel) provider).Abort();
                }
                ((IDisposable) provider).Dispose();
            }
            catch (Exception ex)
            {
                throw new Exception("Error while closing channel", ex);
            }
        }
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class ServiceA : ContractA
    {
        //CALLING SERVICE B


        //uncomment the following line to reuse the same ChannelFactory each time
        //private readonly ChannelFactory<ContractB> pipeFactory=new ChannelFactory<ContractB>(new NetNamedPipeBinding(),new EndpointAddress("net.pipe://localhost/test_service_b"));

        public void DoWork()
        {
            //comment the following line to reuse the same ChannelFactory each time
            var pipeFactory=new ChannelFactory<ContractB>(new NetNamedPipeBinding(),new EndpointAddress("net.pipe://localhost/test_service_b"));

            ContractB provider = null;
            try
            {
                provider = pipeFactory.CreateChannel();
                provider.DoWork();
            }
            catch
            {
            }
            finally
            {
                CloseChannel(provider);

                //comment the following line to reuse the same ChannelFactory each time
                try { pipeFactory.Close(); } catch { pipeFactory.Abort(); }
            }
        }

        private void CloseChannel(ContractB provider)
        {
            try
            {
                if (provider == null)
                    return;
                try
                {
                    ((IClientChannel) provider).Close();
                }
                catch
                {
                    ((IClientChannel) provider).Abort();
                }
                ((IDisposable) provider).Dispose();
            }
            catch (Exception ex)
            {
                throw new Exception("Error while closing channel", ex);
            }
        }

    }

    [ServiceContract]
    public interface ContractA
    {
        [OperationContract]
        void DoWork();
    }

    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    public class ServiceB : ContractB
    {
        public void DoWork()
        {
        }
    }

    [ServiceContract]
    public interface ContractB
    {
        [OperationContract]
        void DoWork();
    }
}

2
catch { } 真的吗?如果你这样做,至少应该注释一下为什么这样做。 - Steve Wellens
2
简单...因为这段代码只是为了说明问题...它不是生产代码 :) - Cedric Mamo
只需打开任务管理器即可看到该进程的内存使用量增加。将其留置一段时间后,它甚至会使用几十亿字节的内存。 - Cedric Mamo
如果在 private static void dowork() 中注释掉 provider.DoWork();,即只打开和关闭通道,会发生什么? - Codor
如果我注释掉那个调用,该进程仍会泄漏内存。它泄漏得更快,但这只是因为该方法执行得更快,我相信。 - Cedric Mamo
显示剩余2条评论
1个回答

1
可能会发生的情况是,您正在比默认工作站垃圾回收器更快地创建对象。.net 3.5 GC的默认延迟模式是交互式的,这意味着为了保持UI响应,如果收集需要太长时间,它会放弃收集。.net 4和4.5中的GC工作方式不同,这可能是您看到增长速度不同的原因。
尝试在App.config中打开服务器垃圾回收模式,看看行为是否有所改变。这应该允许GC一直工作直到完成。
<configuration>
   <runtime>
      <gcServer enabled="true"/>
   </runtime>
</configuration>

我假设您正在使用多核系统,否则这将毫无效果。

不是这个情况,即使我让它运行数小时,内存也从未被回收。这绝对不是垃圾回收问题。即使在每次调用后调用GC.Collect(),泄漏仍然存在。这似乎是WCF中的一个错误,但我找不到解决方法。 - Cedric Mamo
顺便说一下,我实际上尝试了你的建议,但仍然有泄漏。 - Cedric Mamo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接