【问题标题】:WCF memory leak when calling WCF service from within another WCF service从另一个 WCF 服务中调用 WCF 服务时 WCF 内存泄漏
【发布时间】:2014-12-01 22:34:06
【问题描述】:

我注意到 WCF 应用程序中存在内存泄漏问题,并设法在一个简单的程序中复制了它。从另一个 WCF 服务中调用 WCF 服务时会出现此问题。

在以下示例中,我有两个服务AB。当我在服务A 上调用DoWork 方法时,它又调用DoWork 服务B 的方法。

在下面的示例中,我每次都创建一个新的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();
    }
}

【问题讨论】:

  • catch { } 真的吗?如果你做这样的事情,你至少应该说明你为什么这样做。
  • 简单...因为此代码仅用于说明问题...不是生产代码:)
  • 为什么你认为你有内存泄漏?您使用什么工具来做出决定?
  • 只需打开任务管理器即可显示该进程的内存使用率上升。离开了一段时间,它甚至最终使用了千兆字节的内存。
  • 你把private static void dowork()中的provider.DoWork();注释掉会怎样,即只是打开和关闭频道?

标签: c# .net wcf memory-leaks


【解决方案1】:

可能发生的情况是,您创建对象的速度比默认工作站垃圾收集器摆脱它们的速度要快。 .net 3.5 GC 的默认延迟模式是交互式的,这意味着如果收集时间过长以保持 UI 响应,它会放弃。 GC 在 .net 4 和 4.5 中的工作方式不同,这可能就是您看到不同增长率的原因。

尝试在 App.config 中打开服务器垃圾收集模式,看看行为是否改变。这应该允许 GC 工作直到完成。

<configuration>
   <runtime>
      <gcServer enabled="true"/>
   </runtime>
</configuration>

我假设您使用的是多核系统,否则这将产生零效果。

【讨论】:

  • 不是这样,即使我让它运行几个小时,内存也永远不会回收。这绝对不是GC问题。即使我在每次调用后调用 GC.Collect(),泄漏仍然存在。这似乎是 WCF 中的一个错误,但我无法找到解决它的方法。
  • 顺便说一句,我确实尝试过你的建议,但它仍然泄露了
猜你喜欢
  • 2011-02-18
  • 2011-01-31
  • 1970-01-01
  • 2018-12-17
  • 1970-01-01
  • 2015-06-19
  • 1970-01-01
  • 1970-01-01
  • 2019-03-06
相关资源
最近更新 更多