【问题标题】:C# WCF client Memory leak on XPXP上的C# WCF客户端内存泄漏
【发布时间】:2011-09-12 11:51:03
【问题描述】:

我有问题。我编写了一个使用 wsHttpBinding 的 wcf 客户端(WPF 和 c#)。 我正在使用双工。我有一个功能,我每分钟调用一次 wcf 服务,称为 KeepConnection。 而且我从不关闭客户端代理,因为我需要让客户端始终“在线”以进行服务回调。但是当在 Windows XP 上运行这个客户端时,我的应用程序的内存出现了一个奇怪的问题。在 win 7 /vista 上正常运行时,应用程序在任务管理器中只使用了 40mb 的内存。在Xp中每秒不断地增加内存的使用。 我在 2 天内获得了超过 700mb。

有没有办法解决这个问题,或者它与 XP 相关。感谢您的帮助。

服务代码:

    /// <summary>
    /// Just an empty method for client to keep the connection alive with the service.
    /// </summary>
    public void KeepConnection()
    {
        _logger.Debug("Keep alive requested.");
    }

我的客户代码。

    private InstanceContext instanceContext; //Wcf instance context for callbacks.

    public static BootDialog _bootScreen = new BootDialog(); //Boot window.
    public static RetryDialog _retryScreen = new RetryDialog(); //Retry window.

    public static ProductionServiceClient service; //Wcf service client

    public static ClientCallBack clientBack; //Client callback events and handler.
    public static ClientTokenResponse ClientToken; //ClientToken from wcf service.
    public static int[] ScannerNumbers;
    public static IList<HighlightArticleDto> highListArticleList; //List and color of witch list to highligt.
    private static readonly Logger _logger = LogManager.GetCurrentClassLogger();
    private static ClientTokenRequest clientRequest;
    private Timer _keepAliveTimer = new Timer();

    private void Application_Startup(object sender, StartupEventArgs e)
    {
        _logger.Trace("Enter Application_Startup().");

        int[] scannerNumberList = ParseHandler.GetScannersFromSettingString(Settings.Default.Scanners);

        //Saves it globally
        App.ScannerNumbers = scannerNumberList;

        _logger.Info("Getting {0} scanners for this client.", scannerNumberList.Count());

        clientBack = new ClientCallBack();
        instanceContext = new InstanceContext(clientBack);

        //ToDO : This fix is for XP computer with the http://+:80/Temporary_Listen_Addresses/c269764e-808e-4284-ad7f-4e0eb88ee951/ error.
        WSDualHttpBinding binding = new WSDualHttpBinding();
        binding.Name = "WsDualTcpEndpoint";
        binding.CloseTimeout = new TimeSpan(0, 0, 10);
        binding.OpenTimeout = new TimeSpan(0, 0, 10);
        //binding.ReceiveTimeout = new TimeSpan(0, 0, 30);
        binding.SendTimeout = new TimeSpan(0, 0, 10);
        binding.BypassProxyOnLocal = false;
        binding.TransactionFlow = false;
        binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
        binding.MaxBufferPoolSize = 524288;
        binding.MaxReceivedMessageSize = 65536;
        binding.MessageEncoding = WSMessageEncoding.Text;
        binding.TextEncoding = System.Text.Encoding.UTF8;
        binding.UseDefaultWebProxy = false;
        binding.Security.Mode = WSDualHttpSecurityMode.None;

        StringBuilder sb = new StringBuilder();
        sb.Append("http://").Append(GetLocalIp()).Append(":808/WSDualOnXP");

        _logger.Debug("Client base address : {1}.", sb.ToString());

        binding.ClientBaseAddress = new Uri(sb.ToString());

        EndpointAddress endpoint = new EndpointAddress(Settings.Default.ServerAddress);
        service = new ProductionServiceClient(instanceContext, binding, endpoint);

        //2011-08-25 Test utav clientbase
        //service = new ProductionServiceClient(instanceContext, "WsDualTcpEndpoint", Settings.Default.ServerAddress);

        _logger.Debug("Server address : {0}.", Settings.Default.ServerAddress);

        //ToDo Disabled GeneralDialog.
        //2011-05-25 Remove this comment if generaldialog wants to be seen.
        //if (scannerNumberList.Count() == 0 || String.IsNullOrEmpty(Settings.Default.ServerAddress))
        //{
        //    GeneralDialog dialog = new GeneralDialog();
        //    dialog.Show();
        //    return;
        //}

        //Subscribe to wcf service.
        SubscribeToService(scannerNumberList);

        //Keep connection to the service alive.
        KeepAlive();

        //Start timer for highlight list
        GetHighLightListTimer();

        //Catch unhandled exceptions
        this.DispatcherUnhandledException += new System.Windows.Threading.DispatcherUnhandledExceptionEventHandler(App_DispatcherUnhandledException);
    }

    private void KeepAlive()
    {
        _keepAliveTimer.Interval = 31000;
        _keepAliveTimer.Elapsed +=
                        (
            (object o, ElapsedEventArgs args) =>
            {
                try
                {
                    _keepAliveTimer.Stop();

                    if (service.State != CommunicationState.Opened)
                    {
                        if (service != null) { service.Abort(); }
                        ShowRetryDialog();
                        RetryToSubscribe();
                    }

                    service.KeepConnection();
                }
                catch (TimeoutException ex)
                {
                    if (service != null) { service.Abort(); }

                    ShowRetryDialog();
                    RetryToSubscribe();
                }
                catch (CommunicationException ex)
                {
                    if (service.State != CommunicationState.Opened)
                    {
                        if (service != null) { service.Abort(); }

                        ShowRetryDialog();
                        RetryToSubscribe();
                    }
                }
                catch
                {
                    if (service != null) { service.Abort(); }
                    _keepAliveTimer.Stop();

                    ShowRetryDialog();
                    RetryToSubscribe();
                }
                finally
                {
                    _keepAliveTimer.Start();
                }
            }
        );

        _keepAliveTimer.Start();
    }

还有我的客户回调。

    #region ClientCallBacks
    //When service callbacks to the client this methods will be triggered.

    void clientBack_ClientNotified(object sender, ClientNotifiedEventArgs e)
    {
        throw new NotImplementedException();
    }

    void clientBack_RemoveFromDisplayEvent(object sender, RemoveFromDisplayEventArgs e)
    {
        try
        {
            _logger.Info("Remove from display.");

            userControlChairs.Dispatcher.Invoke((Action)(() =>
            {
                _queueProductionItems.Remove(e.OrderResponse);
            }));
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    void clientBack_AddToDisplayEvent(object sender, AddToDisplayEventArgs e)
    {
        try
        {
            _logger.Info("Add to display.");

            userControlChairs.Dispatcher.Invoke((Action)(() =>
            {
                _queueProductionItems.Add(e.OrderResponse);
            }));
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    void clientBack_UpdateQueueDisplayEvent(object sender, UpdateQueueDisplayEventArgs e)
    {
        try
        {
            _logger.Info("Update queue display.");

            userControlQueue.Dispatcher.Invoke((Action)(() =>
            {
                _queueDisplayItems.Clear();
                foreach (OrderDto o in e.UnfinishedOrdersResponse.Orders)
                {
                    _queueDisplayItems.Add(o);
                }

            }));
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }

    #endregion

    private ObservableOrderResponseQueue _queueProductionItems = new ObservableOrderResponseQueue(); //List of the chairs that will be displayed.
    private ObservableCollection<ErrorMessage> _errorMessages = new ObservableCollection<ErrorMessage>(); //List that holds the error message for debug 
    private ObservableCollection<OrderDto> _queueDisplayItems = new ObservableCollection<OrderDto>();//List of order and quanities left. (DisplayQueue).
    private ObservableCollection<DebugInfo> _queueDebugInfo = new ObservableCollection<DebugInfo>(); //

RetryToSubsribe 方法。

        public void RetryToSubscribe()
    {

        try
        {
            WSDualHttpBinding binding = new WSDualHttpBinding();
            binding.Name = "WsDualTcpEndpoint";
            binding.CloseTimeout = new TimeSpan(0, 1, 0);
            binding.OpenTimeout = new TimeSpan(0, 1, 0);
            //binding.ReceiveTimeout = new TimeSpan(0, 0, 30);
            binding.SendTimeout = new TimeSpan(0, 1, 0);
            binding.BypassProxyOnLocal = false;
            binding.TransactionFlow = false;
            binding.HostNameComparisonMode = HostNameComparisonMode.StrongWildcard;
            binding.MaxBufferPoolSize = 524288;
            binding.MaxReceivedMessageSize = 65536;
            binding.MessageEncoding = WSMessageEncoding.Text;
            binding.TextEncoding = System.Text.Encoding.UTF8;
            binding.UseDefaultWebProxy = false;
            binding.Security.Mode = WSDualHttpSecurityMode.None;

            StringBuilder sb = new StringBuilder();
            sb.Append("http://").Append(GetLocalIp()).Append(":808/WSDualOnXP");

            _logger.Debug("Client base address : {1}.", sb.ToString());

            binding.ClientBaseAddress = new Uri(sb.ToString());

            EndpointAddress endpoint = new EndpointAddress(Settings.Default.ServerAddress);

            service = new ProductionServiceClient(instanceContext, binding, endpoint);

            ClientTokenRequest request = new ClientTokenRequest();
            request.RequestId = NewRequestId;
            request.StationNumbers = ScannerNumbers;

            clientRequest = request;

            service.Subscribe(request);

            //Close the retry window.
            this.Dispatcher.Invoke((Action)(() =>
            {
                //Set the background to default.
                this.MainWindow.SetResourceReference(Window.BackgroundProperty, "MainBackground");

                _retryScreen.Hide();
            }));

        }
        catch (Exception ex)
        {
            _logger.Error(ex.Message);
        }

    }

【问题讨论】:

  • 感谢您的网址。但我的服务没有任何问题。仅在客户端。但是我需要关闭代理吗?如果我关闭它,回调会起作用吗?。
  • 据我了解,Duplex 实际上您的客户端也成为了具有自己的通道堆栈的服务。所以如果你关闭从客户端到服务的通道,回调通道应该仍然是打开的。
  • @Tan:没有任何代码,无法分辨。请发布一些服务,更重要的是,客户端代码,以及您的回调设置和回调中的内容。
  • 但奇怪的是,这种情况只发生在 Windows XP 中。在 Windows 7 中,内存使用量不会增加。

标签: c# wpf wcf


【解决方案1】:

如果您可以运行 windbg 并继续操作,您可能会发现究竟是什么泄漏。

  • 在你的 winxp 机器上打开 windbg 并附加到客户端进程(让它运行一段时间以放大问题,即内存泄漏)

  • 在提示符下输入

If using .NET 2/3.5
.loadby sos mscorwks
If using .NET 4 
.loadby sos clr
  • 然后运行

!dumpheap -stat

您将获得按类分组的内存中所有对象的列表。寻找实例数量最多的那些。希望这能让你知道什么是有问题的代码。

其他选项有:

!dumpheap -stat -type 我的班级*

这将只显示以 MyClass 开头的类的实例。

在从进程分离之前不要关闭windbg,否则它会杀死你的进程。

【讨论】:

  • 我尝试使用 windbg 并确实发现 System.String 有 65000 个对象。但奇怪的是,当我将应用程序附加到 windbg 时,应用程序的内存没有增加。但在分离后它又增加了来自windbg的应用程序。
  • 字符串和对象实例可以被忽略,除非这是一个巨大的数字(你需要决定你的情况是多大的数字)。你的琴弦的总尺寸是多少?由于未处理一次性资源,您还可能会泄漏非托管内存。
  • 我没有非托管代码。该应用程序仅调用 wcf 服务。它使用事件和 ObservableCollection 和 INotifyCollectionChanged。这些对象会泄漏内存吗?而且似乎只有虚拟内存在增加。
  • 您在 .net 中执行的每个 I/O 操作都分配了一些非托管资源。您的 wcf 频道正在使用非托管资源,这就是您需要在完成后立即关闭它们的原因。计时器也有对内核对象的引用。大多数非托管内存泄漏情况都是 .NET 框架错误,您对此无能为力。我能问一下为什么你需要让你的服务保持活力吗?投票有什么问题?您有实时应用程序吗?
  • 是的,我有一个实时应用程序。并且客户端计算机每周只关闭一次。我不能使用轮询方法,因为我需要双工。服务正在处理一些 xml 文件并将信息发送给客户端。如果我处置我的服务客户端,双工将不起作用。
【解决方案2】:

我猜测内存泄漏发生在计时器的 Elapsed 处理程序中。你能发布 RetryToSubscribe() 的代码吗?

【讨论】:

  • 使用 RetryToSubscribe() 编辑帖子
  • 您没有在 RetryToSubscribe 中处理任何内容,您可能应该取消注册您的事件。
  • 但是,如果我处置我的服务或取消注册我的活动,则双工将无法工作。我的应用程序需要始终打开。并且需要是实时的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-05-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多