为什么GC System.Threading.OverlappedData需要这么长时间?

时间:2012-09-06 09:14:48

标签: c# memory-leaks garbage-collection asyncsocket

我正在通过内存分析器运行我的应用程序以检查是否存在泄漏。事情似乎有点好,但我得到了很多这些OverlappedData似乎在终结器队列中徘徊,几乎没有任何东西。它们是通过关闭连接两端的基础NetworkStream而取消的重叠IO的结果。

网络流本身已被处理。任何地方都没有NetworkStream的实时实例。

通常它们的根源是OverlappedDataCacheLine。我在回调中调用EndRead是我做的第一件事,所以对BeginRead的调用不应该没有它是对应的EndRead

这是一个非常典型的外观,是谁将其与工具保持一致

OverlappedData is being help up

最后它 得到了GC但它需要永远 - 当我开始大约一千个流时将大约半小时杀死所有内容,将它们放入异步调用中到BeginRead并在大约一分钟后关闭它们。

这个程序在端口80上对网络服务器稍微重现了这个问题。任何网络服务器都会真的这样做。

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        var clients = new List<TcpClient>();
        for (int i = 0; i < 1000; ++i) {
            var client = new TcpClient();
            clients.Add(client);

            client.BeginConnect("localhost", 80, connectResult =>
                {
                    client.EndConnect(connectResult);

                    var stream = client.GetStream();
                    stream.BeginRead(new byte[1000], 0, 1000, result =>
                        {
                            try
                            {
                                stream.EndRead(result);
                                Console.WriteLine("Finished (should not happen)");
                            }
                            catch
                            {
                                // Expect to find an IO exception here
                                Console.WriteLine("Faulted");                               
                            }
                        }, stream);     
                }, client);             
        }

        Thread.Sleep(10000); // Make sure everything has time to connect

        foreach (var tcpClient in clients)
        {
            tcpClient.GetStream().Close();
            tcpClient.Close();
        }
        clients.Clear(); // Make sure the entire list can be GC'd

        Thread.Sleep(Timeout.Infinite); // Wait forever. See in profiler to see the overlapped IO take forever to go away
    }
}

当然,这个程序不需要永远清理千OverlappedData,因为它比真正的应用程序小,但确实需要一段时间才能完成它。在运行真实的东西而不是这个测试应用程序时,我会收到卡住终结器的警告。它在我的应用程序中做的不多,只是尝试关闭可能没有关闭的所有内容,并确保没有任何引用被保存到任何地方。

如果我在客户端上调用Dispose()Close()并且它是流,则根本不重要。结果是一样的。

有关为何发生这种情况以及如何避免这种情况的任何线索? CLR对我来说很聪明,并且保留这​​些固定的内存块可能会为新的呼叫做准备吗?为什么终结者的完成速度如此之慢?

更新在做了一些令人难以置信的愚蠢的负载测试之后,在F5键上放一杯水并喝咖啡,似乎有些东西会在压力下触发更完整的GC来收集这些东西。所以实际上似乎并不是一个真正的问题但仍然很高兴知道这里实际发生了什么,为什么收集这个对象的速度比其他对象慢,如果这可能是一个在后期发布内存碎片等问题。

2 个答案:

答案 0 :(得分:5)

好的,现在看来发生了什么。只有在分配内存时才会发生垃圾收集。它需要至少2兆字节的分配,即生成0 GC堆的典型初始大小以触发GC。换句话说,什么都不做的程序永远不会运行GC,你会看到任何尚未在堆中使用内存分析器收集的对象很长时间。

这是对你描述的内容的一个很好的解释。终止所有连接后,您的程序不再需要执行任何操作。因此,如果任何内存不会分配太多,所以不会触发集合。如果您的探查器没有显示集合,那么您可以使用Perfmon.exe查看它们。这根本不是问题,只是垃圾收集器如何工作的副作用。

当您有明确证据表明程序存在资产消耗问题时,只会担心泄漏。

答案 1 :(得分:0)

尝试从AsyncResult获取客户端以排除任何lambda闭包问题。

TcpClient t = (TcpClient)connectResult.AsyncState;

此外,您是否应该在之后致电EndConnect 来完成处理?